用O3迷惑表达模板Segfault

时间:2015-02-03 22:57:02

标签: c++ expression-templates

在我的gcc-4.8.1上,我用两个命令编译了以下程序:

g++ -Wfatal-errors -std=c++11 -Wall -Werror test.cpp -o test -g
g++ -Wfatal-errors -std=c++11 -Wall -Werror test.cpp -o test -O3 -g

第一个可执行文件具有预期的输出,但第二个可执行文件具有段错误。问题是它很难调试,因为-O3过多地​​使代码混淆了-g调试信息以保留含义,因此gdb无法翻译源代码中发生的事情。所以,我开始插入print语句。正如我所料,print语句会改变结果。使用调试打印,它工作得很好!

这是我的表达模板源:

//test.cpp
#include <vector>
#include <stdlib.h>
#include <iostream>

using namespace std;

typedef vector<int> Valarray;

template<typename L, typename R>
struct BinOpPlus {
  const L& left;
  const R& right;

  BinOpPlus(const L& l, const R& r)
    : left(l), right(r)
  {}

  int operator[](int i) const { 
    int l = left[i];
    //cerr << "Left: " << l << endl; //uncomment to fix segfault
    int r = right[i];
    //cerr << "Right: " << r << endl; //uncomment to fix segfault
    return l + r;
  }
};

template<typename L, typename R>
BinOpPlus<L, R> operator+(const L& left, const R& right){
  return BinOpPlus<L, R>(left, right);
}

int main() {
  //int size = 10000000;
  int size = 10;
  Valarray v[3];
  for(int n=0; n<3; ++n){
    for(int i=0; i<size; ++i){
      int val = rand() % 100;
      v[n].push_back(val);
    }
  }

  auto out = v[0] + v[1] + v[2];

  int sum = 0;
  for(int i=0; i<size; ++i){
    cerr << "Checkpoint!" << endl;
    sum += out[i]; //segfaults here
    cerr << "Sum: " << sum << endl;
  }

  cout << "Sum: " << sum << endl;
  return 0;
}

-O3给我一个不正确/不可靠的二进制文件以来已经有很长一段时间了。我首先假设我在代码中做错了什么,但对-O0显示它没有错。任何人都有任何想法,我做错了什么?

3 个答案:

答案 0 :(得分:1)

在这一行

auto out = v[0] + v[1] + v[2];

out的类型为BinOpPlus< BinOpPlus<ValArray, ValArray>, Valarray>。由于您的BinOpPlus存储对其参数的引用,而BinOpPlus<ValArray,ValArray>存在临时的,因此您有未定义的行为。

通常这些表达模板使用特征来决定如何存储它们的参数,以便您可以通过引用存储实际对象(并假设用户不会搞砸)和其他ET按值(它们非常小) )。

同样使用auto算术ET被认为是至少有问题的,因为它很少产生预期的类型。出于这个原因,有一些提议引入了一种operator auto来自定义ET中自动推断出的类型。

答案 1 :(得分:0)

这是预感。

在第

auto out = v[0] + v[1] + v[2];
你有临时对象。这可能会被-O3标志删除。我会尝试以下方法:

auto out1 = v[1] + v[2];
auto out = v[0] + out1;

答案 2 :(得分:0)

将代码更改为不在结构中使用引用成员。我相信参考成员在这里添加时会使复制操作变得混乱:

auto out = v[0] + v[1] + v[2];

例如:

template<typename L, typename R>
struct BinOpPlus {
  const L left;
  const R right;

进行此更改可正常工作。

此外,仅供参考,在使用Visual Studio 2013编译代码并发出完整警告(/W4)时,我们会收到此警告:

警告C4512:&#39; BinOpPlus&#39; :无法生成赋值运算符。

就在那里,这表明任何复制都可能产生不良影响。


没有引用的良好运行实例:http://ideone.com/JKxoDv

带参考的不良运行实例:http://ideone.com/7oSoJB