c++ - 为什么 GCC 会删除我在 O3 上的代码,而不是在 O0 上?

标签 c++

最近我一直在尝试学习右值和完美转发。在玩弄一些结构时,我在切换编译器和优化级别时遇到了一些特殊的行为。
在没有打开优化的情况下在 GCC 上编译相同的代码会产生预期的结果,但是打开任何优化级别都会导致我的所有代码都被删除。
在没有优化的情况下在 clang 上编译相同的代码也会产生预期的结果。然后在 clang 上打开优化仍然会产生预期的结果。
我知道这会引起未定义的行为,但我只是无法弄清楚到底出了什么问题以及是什么导致了两个编译器之间的差异。

gcc -O0 -std=c++17 -Wall -Wextra
https://godbolt.org/z/5xY1Gz
gcc -O3 -std=c++17 -Wall -Wextra
https://godbolt.org/z/fE3TE5
clang -O0 -std=c++17 -Wall -Wextra
https://godbolt.org/z/W98fh8
clang -O3 -std=c++17 -Wall -Wextra
https://godbolt.org/z/6sEo8j
#include <utility>

// lambda_t is the type of thing we want to call.
// capture_t is the type of a helper object that 
// contains all all parameters meant to be passed to the callable
template< class lambda_t, class capture_t >
struct CallObject {

    lambda_t  m_lambda;
    capture_t m_args;

    typedef decltype( m_args(m_lambda) ) return_t;

    //Construct the CallObject by perfect forwarding which is
    //neccessary as they may these are lambda which will have
    //captured objects and we dont want uneccessary copies
    //while passing these around
    CallObject( lambda_t&& p_lambda, capture_t&& p_args ) :
        m_lambda{ std::forward<lambda_t>(p_lambda) },
        m_args  { std::forward<capture_t>(p_args) }
    {

    }

    //Applies the arguments captured in m_args to the thing
    //we actually want to call
    return_t invoke() {
        return m_args(m_lambda);
    }

    //Deleting special members for testing purposes
    CallObject() = delete;
    CallObject( const CallObject& ) = delete;
    CallObject( CallObject&& ) = delete;
    CallObject& operator=( const CallObject& ) = delete;
    CallObject& operator=( CallObject&& ) = delete;
};

//Factory helper function that is needed to create a helper
//object that contains all the paremeters required for the 
//callable. Aswell as for helping to properly templatize
//the CallObject
template< class lambda_t, class ... Tn >
auto Factory( lambda_t&& p_lambda, Tn&& ... p_argn ){

    //Using a lambda as helper object to contain all the required paramters for the callable
    //This conviently allows for storing value, references and so on
    auto x = [&p_argn...]( lambda_t& pp_lambda ) mutable -> decltype(auto) {

        return pp_lambda( std::forward<decltype(p_argn)>(p_argn) ... );
    };

    typedef decltype(x) xt;
    //explicit templetization is not needed in this case but
    //for the sake of readability it needed here since we then
    //need to forward the lambda that captures the arguments
    return CallObject< lambda_t, xt >( std::forward<lambda_t>(p_lambda), std::forward<xt>(x) );
}

int main(){

    auto xx = Factory( []( int a, int b ){

        return a+b;

    }, 10, 3 );

    int q = xx.invoke();

    return q;
}

最佳答案

如果发生这样的事情,通常是因为您在程序的某个地方有未定义的行为。编译器确实检测到了这一点,并且在积极优化时会因此丢弃整个程序。
在您的具体示例中,您已经以编译器警告的形式得到了一些不太正确的提示:

<source>: In function 'int main()':
<source>:45:18: warning: '<anonymous>' is used uninitialized [-Wuninitialized]
   45 |         return a+b;
      |                  ^
这怎么可能发生?什么可能导致b此时未初始化?
由于b此时是函数参数,问题必须出在该 lambda 的调用者身上。检查调用站点,我们注意到一些可疑的地方:
auto x = [&p_argn...]( lambda_t& pp_lambda ) mutable -> decltype(auto) {
    return pp_lambda( std::forward<decltype(p_argn)>(p_argn) ... );
};
绑定(bind)到 b 的参数作为参数包传递 p_argn .但请注意该参数包的生命周期:它是通过引用捕获的!所以这里没有完美的转发,尽管你写了std::forward在 lambda 主体中,因为您在 lambda 中通过引用捕获,并且 lambda 不会“看到”在其主体之外在周围函数中发生的情况。 a 会遇到同样的生命周期问题这里也是,但出于某种原因,编译器选择不提示那个。这对您来说是未定义的行为,无法保证您会收到警告。解决此问题的最快方法是仅按值捕获参数。您可以使用命名捕获保留完美的转发属性,语法有些特殊:
auto x = [...p_argn = std::forward<decltype(p_argn)>(p_argn)]( lambda_t& pp_lambda ) mutable -> decltype(auto) {
    return pp_lambda(std::move(p_argn)... );
};
确保您了解在这种情况下实际存储的内容,甚至可以绘制图片。在编写这样的代码时,能够准确地知道各个对象所在的位置至关重要,否则很容易编写这样的终身错误。

关于c++ - 为什么 GCC 会删除我在 O3 上的代码,而不是在 O0 上?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63351841/

相关文章:

c++ - 如何在c++中包含头文件?

c++ - 运算符返回冲突的大小

c++ - 修改 OpenGL 中的单个顶点位置

c++ - 从 C++ 中的网站读取文本

c++ - Google Test Discovery在Mac OS X上两次添加并运行了我所有的测试

c++ - 将 rcpp 变量转换为标准 C++ 变量

c++ - Qt Creator找不到丢失的库

64 位系统内核模块中的 C++ 支持

c++ - Ros安装在构建过程中卡住

c++ - 在构造函数类中初始化 ofstream,仅适用于 c++11