当我用valarray
编写一个简单的算术表达式并将结果赋给auto
时,当我尝试在gcc上访问结果时,我得到一个段错误。
#include <iostream>
#include <valarray>
using std::ostream; using std::valarray;
ostream& operator<<(ostream&os, const valarray<double>&vs) {
os << "[";
for(auto&v : vs) os << v << " ";
return os << "]";
}
int main() {
valarray<double> a{ 1.0, 2.0, 3.0, 4.0 };
std::cout << "a: " << a << "\n";
valarray<double> b{ 2.0, 4.0, 6.0, 8.0 };
std::cout << "b: " << b << "\n";
valarray<double> c{ 2.0, 1.5, 0.5, 0.25 };
std::cout << "c: " << c << "\n";
valarray<double> x = ( a + b ) / 2;
std::cout << "x: " << x << "\n";
// this still works:
auto y = ( a + b ) / 2;
// The following will result in a segfault:
std::cout << "y:" << y << "\n";
}
reference表示实现可能会选择算术运算重载的返回类型可能不是valarray
- 值,而是&#34;表现得像#34;:< / p>
允许按值返回valarray的运算符返回不同类型的对象。这种类型需要隐式转换为valarray,并作为所有函数的参数支持valarray&amp;参数。这允许写时复制实现。
好吧,我的operator<<
应该要求#34;隐式转换&#34;,不应该吗?
那么为什么我会遇到段错?
$ ./valarray01.cpp.x
a: [1 2 3 4 ]
b: [2 4 6 8 ]
c: [2 1.5 0.5 0.25 ]
x: [1.5 3 4.5 6 ]
Segmentation fault (core dumped)
gcc版本6.2.0 20160901(Ubuntu 6.2.0-3ubuntu11~14.04)
当我尝试 clang (在linux上,所以可能是gcc&#39; s stdlib)时我对此持怀疑态度......并且......它有效:
clang version 3.9.1-svn288847-1~exp1(branches / release_39)
$ ./valarray01.cpp.x
a: [1 2 3 4 ]
b: [2 4 6 8 ]
c: [2 1.5 0.5 0.25 ]
x: [1.5 3 4.5 6 ]
y:[1.5 3 4.5 6 ]
好吧,在我提交gcc-bug之前......我做错了什么?我的auto
邪恶?或者它真的是gcc吗?
答案 0 :(得分:2)
这是因为GCC的valarray
实现使用Expression Templates来避免为算术表达式的中间结果创建临时对象。表达式模板和auto
混合不好。
( a + b )
不会立即执行乘法,而是会创建一个&#34;闭包&#34;引用a
和b
的对象。实际的乘法将被延迟,直到在需要结果的上下文中使用闭包。接下来,表达式( a + b ) / 2
的其余部分创建第二个闭包对象,该对象包含对第一个闭包对象的引用,以及对值2
的引用。然后使用第二个闭包对象初始化一个由auto
推导出的类型的变量:
auto y = ( a + b ) / 2;
所以y
是一个闭包对象,它引用了第一个闭包,并引用了int
,其值为2
。但是,第一个闭包和int
值都是临时值,在声明结尾处超出范围。这意味着y
有两个悬空引用,一个临时闭包和一个临时int
。当您尝试在y
语句中使用cout
时,它会转换为valarray<double>
,它会尝试评估乘法和除法的结果。该评估遵循悬空引用并尝试访问不再存在的临时对象。这意味着未定义的行为。
我正在开发GCC补丁,这将有助于使这样的代码更不容易出错(Bug 83860),尽管将auto
与表达式模板结合起来仍然很脆弱
如果您不使用auto
,即
std::valarray<double> y = (a+b)/2;
这里表达模板在临时表超出范围之前进行评估,因此没有悬空引用。
这个特殊的例子可以用于&#34;工作&#34;通过编译-fstack-reuse=none
来禁用重用临时对象使用的堆栈空间的优化。这意味着悬挂引用仍可用于在终身结束后访问临时对象。这只是一个创可贴,而不是一个真正的解决方案。真正的解决方案是不要混合表达式模板和auto
。