具有算术运算的valarray返回类型

tow*_*owi 12 c++ gcc g++ valarray

当我写一个简单的算术表达式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";
}
Run Code Online (Sandbox Code Playgroud)

引用表示实现可能会选择算术运算重载的返回类型可能不是一个valarray值,而是"行为类似":

按值返回valarray的运算符允许返回不同类型的对象.这种类型需要隐式转换为valarray,并且作为参与valarray和参数的所有函数的参数.这允许写时复制实现.

好吧,我operator<<应该呼吁"隐含转换",不是吗?

那么为什么我会遇到段错误呢?

$ ./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)
Run Code Online (Sandbox Code Playgroud)

gcc版本6.2.0 20160901(Ubuntu 6.2.0-3ubuntu11~14.04)

当我尝试使用clang(在Linux上,所以可能是gcc的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 ]
Run Code Online (Sandbox Code Playgroud)

好吧,在我提交gcc-bug之前......我做错了什么?是我的auto邪恶?还是真的是gcc?

Jon*_*ely 2

发生这种情况是因为 GCC 的valarray实现使用表达式模板来避免为算术表达式的中间结果创建临时对象。表达式模板并auto不能很好地混合。

发生的情况是,它( a + b )不会立即执行乘法,而是创建一个引用 和 的“闭包”a对象b。实际的乘法将被延迟,直到在需要结果的上下文中使用闭包为止。接下来,表达式的其余部分( a + b ) / 2创建第二个闭包对象,该对象保存对第一个闭包对象的引用以及对 value 的引用2。然后使用第二个闭包对象来初始化一个变量,该变量的类型由以下推导得出auto

auto y = ( a + b ) / 2;
Run Code Online (Sandbox Code Playgroud)

一个闭包对象也是如此y,它引用了第一个闭包和一个intwith value 2。但是,第一个闭包和int值都是临时的,在语句末尾超出了范围。这意味着y有两个悬空引用,一个是临时闭包,一个是临时int. 当您尝试ycout语句中使用时,它会转换为 a valarray<double>,尝试评估乘法和除法的结果。该评估遵循悬空引用并尝试访问不再存在的临时对象。这意味着未定义的行为。

我正在为 GCC 开发一个补丁,该补丁将有助于使此类代码不易出错(针对Bug 83860auto ),尽管它与表达式模板结合起来仍然很脆弱。

如果您不使用autoie ,该代码可以正常工作

std::valarray<double> y = (a+b)/2;
Run Code Online (Sandbox Code Playgroud)

这里,表达式模板在临时变量超出范围之前进行评估,因此不存在悬空引用。

这个特定的例子可以通过编译来“工作”,-fstack-reuse=none禁用重用临时对象所使用的堆栈空间的优化。这意味着悬空引用在其生命周期结束后仍可用于访问临时引用。这只是一个创可贴,而不是真正的解决方案。真正的解决方案是不要混合表达式模板和auto.