为什么GCC生成的代码会从堆栈中读取垃圾?

时间:2015-06-26 12:56:28

标签: c++ gcc assembly optimization

考虑以下代码(使用Eigen):

#include <Eigen/Dense>
#include <iostream>

template<int rows, int cols, int row, class R, class Rv, int N, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==0>::type
    setRow(Eigen::Matrix<R,rows,cols>&)
{}

template<int rows, int cols, int row, class R, class Rv, int N=0, class... Rs>
inline typename std::enable_if<sizeof...(Rs)==cols-N-1>::type
    setRow(Eigen::Matrix<R,rows,cols>& m, Rv val, Rs...args)
{
    m(row,N)=val;
    setRow<rows,cols,row,R,Rv,N+1>(m,args...);
}
template<class T, int R, int C, int CUR_ROW>
class MatrixConstructor
{
    Eigen::Matrix<T,R,C> m;
public:
    MatrixConstructor(const Eigen::Matrix<T,R,C>& m)
        : m(m)
    {}
    MatrixConstructor()
    {}
    template<class...Ts>
    typename std::enable_if<sizeof...(Ts)==C && CUR_ROW<R-1,
    MatrixConstructor<T,R,C,CUR_ROW+1>>::type operator()(Ts... vals)
    {
        setRow<R,C,CUR_ROW>(m,vals...);                                                                                                                                                                                 
        return MatrixConstructor<T,R,C,CUR_ROW+1>(m);
    }

    template<class...Ts>
    typename std::enable_if<sizeof...(Ts)==C && CUR_ROW==R-1,
    Eigen::Matrix<T,R,C>>::type operator()(Ts... vals)
    {
        setRow<R,C,CUR_ROW>(m,vals...);
        return m;
    }
};

void test()
{
    Eigen::Matrix<double,4,3> m=MatrixConstructor<double,4,3,0>()(1,2,3)
                                                                 (4,5,6)
                                                                 (7,8,9)
                                                                 (5,4,3);
    std::cout << m;
}

int main()
{ test(); }

我使用gcc-4.8编译它,并使用完全优化和生成汇编列表的选项。这是我使用的命令:

g++ minitest.cpp -I/usr/include/eigen3 -std=c++0x -O3 -march=native -S -masm=intel

(我的CPU是Intel(R)Xeon(R)CPU E3-1226 v3,运行64位Linux系统 - 希望现在-march=native对读者有意义。)

让我想知道的是,使用此命令行生成的某些指令似乎是荒谬的,甚至是多余的。参见例如设置堆栈后test()函数的启动方式(test()main()的完整代码请参阅here):

vmovsd   xmm4, QWORD PTR .LC6[rip] # 1.0
lea      rsi, [rsp+96]
vmovsd   xmm5, QWORD PTR .LC7[rip] # 2.0
vmovsd   QWORD PTR [rsp], xmm4
vmovapd  xmm3, XMMWORD PTR [rsp+16] # What does it read?!
vmovapd  xmm1, XMMWORD PTR [rsp]    # And this!
vmovsd   QWORD PTR [rsp+32], xmm5
vmovsd   xmm0, QWORD PTR .LC8[rip] # 3.0
vmovapd  XMMWORD PTR [rsp+304], xmm3 # And now even save this junk?!
vmovapd  XMMWORD PTR [rsp+192], xmm1
vmovapd  xmm3, XMMWORD PTR [rsp+48]
vmovapd  xmm1, XMMWORD PTR [rsp+32]
vmovsd   QWORD PTR [rsp+64], xmm0
vmovsd   xmm7, QWORD PTR .LC12[rip] # 7.0
vmovapd  XMMWORD PTR [rsp+336], xmm3
vmovapd  XMMWORD PTR [rsp+224], xmm1
vmovapd  xmm3, XMMWORD PTR [rsp+80]
vmovsd   QWORD PTR [rsp+304], xmm7   # Even stranger — overwrites the junk

我已经在调试器中逐步完成了对这些垃圾的读取,并确认在它们之后xmm3xmm1寄存器确实具有无意义的值。看看这个未定义值的读数,我开始怀疑我的程序确实试图访问一些应该无法访问的内存。但为什么?我在某处介绍过UB吗?

我也试过运行使用-fsanitize=address编译的程序,但它没有任何崩溃。

1 个答案:

答案 0 :(得分:4)

您的代码采取以下步骤:

  1. 使用未初始化的Eigen :: Matrix成员创建未初始化的MatrixConstructor对象
  2. 设置Eigen :: Matrix成员的一行
  3. 创建一个新的MatrixConstructor对象,其Eigen :: Matrix成员使用旧的MatrixConstruction对象&#39; s Eigen :: Matrix成员进行初始化
  4. 因此,在第3步中,您将复制仅设置了第一行的Eigen :: Matrix对象。对象值的其余部分仍然未初始化。由于这些都是临时对象,因此它们都被分配到堆栈中,因此您从堆栈中读取垃圾并不奇怪。

    请注意,这假设Eigen :: Matrix()构造函数没有初始化对象,从快速查看默认情况下它似乎没有做的来源。