【问题标题】:-O2 and -fPIC option in gccgcc 中的 -O2 和 -fPIC 选项
【发布时间】:2021-07-27 15:43:03
【问题描述】:

为了性能优化,我想利用字符串的引用而不是它的值。根据编译选项,我得到不同的结果。这种行为对我来说有点不清楚,我不知道导致这种差异的实际 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。

【问题讨论】:

    标签: c++ gcc compiler-flags


    【解决方案1】:

    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 中你应该观察你所追求的行为。

    【讨论】:

    • 感谢您的快速回复。当然,不应在 main 中使用这些指针(cout 仅用于演示)。其目的是专门利用test2 中的指针,而我曾预料到,指针总是不同的。天真地,我希望编译器将“文本”复制到静态地址,这将非常低效。
    • @Daniel 编译器可以将空间重用于它必须在每个表达式中创建的临时std::string(从"text" 初始化)。编译器知道临时的(std::string)在每个完整表达式的末尾被删除,所以它不需要保留更多的堆栈空间。
    • 我们也可以通过reinterpret_cast&lt;std::uintptr_t&gt;(&amp;in)来避免无效指针问题。但同样,我不会对这些临时对象的地址是否相同或不同做出任何假设,即使使用像 gcc 这样的特定编译器也是如此。
    • @rustyx。您对标准(C++11 §2.14.5 字符串文字)的引用完全回答了我的问题。谢谢!即使使用const char*,也不能保证两个调用将两个不同的指针传递给该函数,因为 gcc 会合并相同的字符串值。不过,如果有机会在 C++ 中为每个函数调用获取一个唯一的指针,那就太好了。
    猜你喜欢
    • 2011-07-15
    • 1970-01-01
    • 2011-04-02
    • 1970-01-01
    • 2020-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多