【问题标题】:Befuddling Expression Template Segfault with O3令人困惑的表达式模板 Segfault 与 O3
【发布时间】:2015-02-04 07:32:14
【问题描述】:

在我的 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 很难翻译源代码中发生的事情。所以,我开始插入打印语句。正如我所料,打印语句改变了结果。使用调试打印,它工作得很好!

这是我的表达式模板来源:

//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 显示出来。有人知道我做错了什么吗?

【问题讨论】:

  • 为什么哦,为什么有人要这样做?
  • 有趣的是,您将运行此代码导致的分段错误称为“令人费解的”。我称之为“令人困惑”的是(1)这种代码背后的动机,以及(2)你认为main中的auto实际上有什么类型(我实际上不知道答案这个)。
  • @Andrey 我没有看到问题。 ET 算术的一般概念对我来说似乎是合理的,它只是一个实现问题。
  • @Andrew (1) 向量算术,无需将向量复制到临时向量等。求和不需要使用表达式模板分配向量,而更直观的方法是创建一个或两个额外的临时向量,并为累加创建另外 1 个额外的局部向量。如果没有内存分配,累积会快得多。 (2) sbabbi 是正确的,因为它是BinOpPlus&lt; BinOpPlus&lt;ValArray, ValArray&gt;, Valarray&gt;。不过,表达式模板类型确实无关紧要。

标签: c++ expression-templates


【解决方案1】:

在这一行

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

out 的类型是BinOpPlus&lt; BinOpPlus&lt;ValArray, ValArray&gt;, Valarray&gt;。由于您的 BinOpPlus 存储对其参数的引用,并且 BinOpPlus&lt;ValArray,ValArray&gt; 有一个临时的,因此您有未定义的行为。

通常像这样的表达式模板使用 trait 来决定如何存储它们的参数,这样你就可以通过引用存储实际对象(并假设用户不会搞砸)和其他 ET 值(无论如何它们都非常小)。

将auto 与算术ET 一起使用至少被认为是有问题的,因为它很少产生预期的类型。正是出于这个原因,有几个提议要引入一种operator auto 来自定义 ET 中由 auto 推导出的类型。

【讨论】:

    【解决方案2】:

    这是一种预感。

    排队

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

    你有临时对象。这可能会被-O3 标志删除。我会尝试以下方法:

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

    【讨论】:

      【解决方案3】:

      更改代码以不在结构中使用引用成员。我相信当您在此处进行添加时,参考成员会使复制操作变得混乱:

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

      例如:

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

      进行此更改可以正常工作。

      另外,仅供参考,当使用带有完整警告 (/W4) 的 Visual Studio 2013 编译您的代码时,我们会收到以下警告:

      警告 C4512:“BinOpPlus”:无法生成赋值运算符。

      因此,这表明任何复制都可能产生不良影响。


      没有参考的良好运行的实时示例:http://ideone.com/JKxoDv

      带有引用的不良运行示例:http://ideone.com/7oSoJB

      【讨论】:

      • 根据我的经验,Visual Studio 2013 的模板内容不好。但是,本练习的全部目的是不复制向量。然而,正如 sbabbi 和 R Sahu 指出的那样,有必要复制右值引用。我忽略了这个细节。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-16
      • 2016-02-23
      • 1970-01-01
      • 1970-01-01
      • 2011-01-27
      • 1970-01-01
      相关资源
      最近更新 更多