4

The following code results in SIGSEGV and I cannot understand why is that.

#include <iostream>
using namespace std;

struct C {
    C(int x) { ptr = new int(x); }
    C(C&& c) { ptr = c.ptr; c.ptr = nullptr; }

    int* ptr;
};

void foo(int* x, C c) {
    cout << *x  << endl;
}

int main() {
    C c(10);
    foo(c.ptr, std::move(c));   
    return 0;
}

I would expect the pointer c.ptr to be passed by value to the function foo, however it behaves like its passed by reference.

Now, if I change the ordering of arguments: void foo(C c, int* x), than the problem disappears. The other solution is to create a local copy of c.ptr before calling to x, and than passing that local copy to foo.

I would like to understand why can't i pass c.ptr by value in the sample code above.

Buyuk
  • 1,094
  • 1
  • 8
  • 23

2 Answers2

6

It is passed by value, however:

foo(c.ptr, std::move(c));

It is unspecified in which order the parameters that are passed to a function call get evaluated.

Order of evaluation of the operands of almost all C++ operators (including the order of evaluation of function arguments in a function-call expression ... ) is unspecified.

"Unspecified" means that they can be evaluated in any order. The order can even be different each time you run the program. Your compiler chose to generate code that evaluates the 2nd parameter with the std::move first. As such, your move constructor moves the pointer out of the object, setting it to null. Then, c.ptr gets evaluated, passing the now-null pointer by value.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Is there any way to force evaluating first argument before move which would not rely on compiler implementation details without creating temporary object? – Buyuk Dec 15 '17 at 12:23
  • 1
    @Buyuk You can evaluate it yourself and store the result. `int*x = c.ptr; foo(x, std::move(c));` – François Andrieux Dec 15 '17 at 13:08
  • @FrançoisAndrieux I know, but i wonder if there is a way to do it without creating temporary object (in this case pointer), because sometimes it can be expensive. – Buyuk Dec 15 '17 at 13:18
  • 3
    @Buyuk You can't force the order of evaluation of function parameters. – François Andrieux Dec 15 '17 at 13:19
  • 1
    Sometimes it is erroneously assumed that temporary objects are expensive. By carefully structuring the code to make it clear to the compiler what are the lifetimes of the objects, modern compilers employ sophisticated code analysis and optimization algorithms that often make it possible for the compiler to deduce the object's limited lifetime; and thus employ the same move semantics that temporary objects would use, thus achieving your desired goals, but using explicit evaluation order. – Sam Varshavchik Dec 15 '17 at 14:59
3

When you call foo(c.ptr, std::move(c)); you can't be sure rather c.ptr or std::move(c) will be evaluated first. The order arguments are evaluated in a function call is unspecified. It seems in your case that std::move(c) will be evaluated first, leaving c.ptr as nullptr. You then do cout << *x << endl which attempts to dereference *x, where x is nullptr.

François Andrieux
  • 28,148
  • 6
  • 56
  • 87