The language does not guarantee you that the right-hand side subexpression func(&ptr)
in
*ptr = func(&ptr);
is evaluated first, and the left-hand side subexpression *ptr
is evaluated later (which is apparently what you expected to happen). The left-hand side can legally be evaluated first, before call to func
. And this is exactly what happened in your case: *ptr
got evaluated before the call, when ptr
was still pointing to x
. After that the assignment destination became finalized (i.e. it became known that the code will assign to x
). Once it happens, changing ptr
no longer changes the assignment destination.
So, the immediate behavior of your code is unspecified due to unspecified order of evaluation. However, one possible evaluation schedule leads to undefined behavior by causing a null pointer dereference. This means that in general case the behavior is undefined.
If I had to model the behavior of this code in terms of C++ language, I'd say that the process of evaluation in this case can be split into these essential steps
1a. int &lhs = *ptr; // evaluate the left-hand side
1b. int rhs = func(&ptr); // evaluate the right-hand side
2. lhs = rhs; // perform the actual assignment
(Even though C language does not have references, internally it uses the same concept of "run-time bound lvalue" to store the result of evaluation of left-hand side of assignment.) The language specification allows enough freedom to make steps 1a and 1b to occur in any order. You expected 1b to occur first, while your compiler decided to start with 1a.