使用 O3 解决表达式模板段错误
Befuddling Expression Template Segfault with O3
在我的 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
给我一个 incorrect/unreliable 二进制文件已经很久了。我首先假设我在我的代码中做错了什么,但还不足以让 -O0
显示出来。有人知道我做错了什么吗?
这是预感。
行中
auto out = v[0] + v[1] + v[2];
你有临时对象。这可能会被 -O3
标志删除。我会尝试以下操作:
auto out1 = v[1] + v[2];
auto out = v[0] + out1;
在这一行
auto out = v[0] + v[1] + v[2];
out
的类型是BinOpPlus< BinOpPlus<ValArray, ValArray>, Valarray>
。由于您的 BinOpPlus
存储对其参数的引用,并且 BinOpPlus<ValArray,ValArray>
存在临时的,因此您具有未定义的行为。
通常像这样的表达式模板使用特征来决定如何存储它们的参数,这样你就可以通过引用存储实际对象(并假设用户不会搞砸)和其他 ET 按值存储(它们非常反正很小)。
同时使用 auto
算术 ET 至少被认为是有问题的,因为它很少产生预期的类型。出于这个原因,已经有一些提议引入一种 operator auto
来自定义 ET 中 auto 推导的类型。
更改代码以不在结构中使用引用成员。我相信当您在此处进行添加时,参考成员会使复制操作变得混乱:
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:'BinOpPlus':无法生成赋值运算符。
就在那里,这表明任何复制都可能产生不良影响。
没有参考文献的好例子 运行:http://ideone.com/JKxoDv
错误的实例 运行 参考文献:http://ideone.com/7oSoJB
在我的 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
给我一个 incorrect/unreliable 二进制文件已经很久了。我首先假设我在我的代码中做错了什么,但还不足以让 -O0
显示出来。有人知道我做错了什么吗?
这是预感。
行中
auto out = v[0] + v[1] + v[2];
你有临时对象。这可能会被 -O3
标志删除。我会尝试以下操作:
auto out1 = v[1] + v[2];
auto out = v[0] + out1;
在这一行
auto out = v[0] + v[1] + v[2];
out
的类型是BinOpPlus< BinOpPlus<ValArray, ValArray>, Valarray>
。由于您的 BinOpPlus
存储对其参数的引用,并且 BinOpPlus<ValArray,ValArray>
存在临时的,因此您具有未定义的行为。
通常像这样的表达式模板使用特征来决定如何存储它们的参数,这样你就可以通过引用存储实际对象(并假设用户不会搞砸)和其他 ET 按值存储(它们非常反正很小)。
同时使用 auto
算术 ET 至少被认为是有问题的,因为它很少产生预期的类型。出于这个原因,已经有一些提议引入一种 operator auto
来自定义 ET 中 auto 推导的类型。
更改代码以不在结构中使用引用成员。我相信当您在此处进行添加时,参考成员会使复制操作变得混乱:
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:'BinOpPlus':无法生成赋值运算符。
就在那里,这表明任何复制都可能产生不良影响。
没有参考文献的好例子 运行:http://ideone.com/JKxoDv
错误的实例 运行 参考文献:http://ideone.com/7oSoJB