gcc 中的 -O2 和 -fPIC 选项

问题描述

为了性能优化,我想使用字符串的引用而不是它的值。根据编译选项的不同,我得到了不同的结果。这种行为对我来说有点不清楚,我不知道导致这种差异的实际 gcc 标志。

我的代码

#include <string>
#include <iostream>

const std::string* test2(const std::string& in) {
   // Here I want to make use of the pointer &in
   // ...
   // it's returned only for demonstration purposes...
   return &in;
}

int main() {
   const std::string* t1 = test2("text");
   const std::string* t2 = test2("text");
   // only for demonstration,the cout is printed....
   std::cout<<"References are: "<<(t1==t2?"equivalent. ":"different. ")<<t1<<"\t"<<t2<<std::endl;
   return 0;
}

共有三个编译选项:

gcc main.cc -o main -lstdc++ -O0 -fPIC && ./main 
gcc main.cc -o main -lstdc++ -O2 -fno-PIC && ./main 
gcc main.cc -o main -lstdc++ -O2 -fPIC && ./main 

前两个产生等效结果(References are: different.),因此指针不同,但第三个产生等效指针(References are: equivalent.)。 为什么会发生这种情况,我必须将哪个选项添加到选项 -O2 -fPIC 以便指针再次不同? 由于此代码嵌入到更大的框架中,因此我无法删除选项 -O2-fPIC

由于我使用选项 -O2-fPIC 获得了想要的结果,但是如果同时使用这两个标志会产生不同的行为,因此我不清楚这些标志的确切行为。>

我尝试过 gcc4.8 和 gcc8.3。

解决方法

t1t2 都是悬空指针,它们指向一个已经被销毁的临时 std::string。临时 std::string 是在每次调用 test2("text") 期间根据字符串文字构造的,并且一直存在到完整表达式(;)的结尾。

它们的确切值取决于编译器如何(重新)使用特定优化级别的堆栈空间。

我必须向选项 -O2 -fPIC 添加哪个选项才能使指针再次不同?

代码显示 undefined behavior,因为它是 illegal to compare invalid pointer values。不要这样做。

如果我们忽略比较部分,那么我们最终会得到这个版本:

#include <string>
#include <iostream>

void test2(const std::string& in) {
   std::cout << "Address of in: " << (void*)&in << std::endl;
}

int main() {
   test2("text");
   test2("text");
}

现在这段代码没有 UB,它会打印相同的地址或不同的地址,这取决于编译器如何在函数调用之间重新使用堆栈空间。没有办法控制这一点,但没有问题,因为跟踪临时地址的地址一开始就不是一个好主意。

您可以尝试使用 const char* 作为输入参数,然后在调用 test2("text") 时不会创建临时文件。但同样,"text" 的两个实例是否指向同一位置是 implementation-defined。尽管 GCC 确实合并了相同的字符串文字,因此至少在 GCC 中您应该观察您所追求的行为。