Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

valarray with arithmetic operations return type

When I write a simple arithmetic expression with valarray and assign the result to auto I get a segfault when I try to access the result on 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";
}

The reference says that the implementation may choose that the return type of the arithmetic operation overloads may not be a valarray-value but something that "behaves like it":

The operators returning a valarray by value are allowed to return an object of a different type instead. Such a type is required to be implicitly convertible to valarray and be supported as argument for all functions taking valarray& arguments. This allows copy-on-write implementations.

Well, my operator<< should call for that "implicit conversion", shouldnt it?

So why do I get a segfault?

$ ./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 version 6.2.0 20160901 (Ubuntu 6.2.0-3ubuntu11~14.04)

I got sceptical when I tried clang (on linux, so probably gcc's stdlib) and... it works:

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 ]

Well, before I file a gcc-bug... am I doing something wrong? Is my auto evil? Or is it really gcc?

like image 824
towi Avatar asked Mar 10 '17 20:03

towi


1 Answers

This happens because GCC's valarray implementation uses Expression Templates to avoid temporary objects being created for intermediate results of arithmetic expressions. Expression templates and auto do not mix well.

What happens is that ( a + b ) does not perform the multiplication immediately, instead it creates a "closure" object which has references to a and b. The actual multiplication will be delayed until the closure is used in a context that requires the result. Next, the rest of the expression ( a + b ) / 2 creates a second closure object which holds a reference to the first closure object, and a reference to the value 2. That second closure object is then used to initialize a variable with the type deduced by auto:

auto y = ( a + b ) / 2;

So y is a closure object that has a reference to the first closure, and to an int with value 2. However, the first closure and the int value were both temporaries, which go out of scope at the end of the statement. This means that y has two dangling references, to a temporary closure and to a temporary int. When you try to use y in the cout statement it gets converted to a valarray<double> which tries to evaluate the results of the multiplication and the division. That evaluation follows the dangling references and tries to access temporary objects that no longer exist. That means undefined behaviour.

I'm working on a patch for GCC that will help make code like this less error-prone (for Bug 83860), although it will still be fragile to combine auto with expression templates.

The code works fine if you don't use auto i.e.

std::valarray<double> y = (a+b)/2;

Here the expression templates get evaluated before the temporaries go out of scope, and so there are no dangling references.

This particular example can be made to "work" by compiling with -fstack-reuse=none which disables the optimizations that reuse the stack space used by the temporary objects. This means the dangling references can still be used to access the temporaries after their lifetime ends. This is just a band-aid, not a real solution. The real solution is to not mix expression templates and auto.

like image 102
Jonathan Wakely Avatar answered Nov 11 '22 16:11

Jonathan Wakely