1

Let's say we have function g:

int g(int x, int& y)
{
    y = y + x++;
    return x + y;
}

And main function:

int main()
{
    int x = 5;
    int y = 2;
    cout << g(g(x, y), y) << ' ';
    cout << x << ' ' << y << endl;
}

It prints expected result:

 34 5 20

But when I rewrote main:

int main()
{
    int x = 5;
    int y = 2;
    cout << g(g(x, y), y) << ' ' << x << ' ' << y << endl;
}

It prints

34 5 2

Can someone please explain me why we have different behavior in these two situations ?

Sam
  • 105
  • 1
  • 7
  • 1
    Because you're using a pre-C++17 compiler? – Cheers and hth. - Alf Jul 23 '18 at 21:53
  • your first function will not compile - `a` is undeclared – Fureeish Jul 23 '18 at 21:53
  • Voted to close as lacking a (potentially) reproducable example. – Cheers and hth. - Alf Jul 23 '18 at 21:54
  • @Fureeish edited – Sam Jul 23 '18 at 21:55
  • 1
    @Sam: For the C++17 rules, check out (https://stackoverflow.com/a/50361541/464581). Unfortunately that's not the selected "solution" to the question, otherwise we could close this one with that question as duplicate. – Cheers and hth. - Alf Jul 23 '18 at 22:09
  • Reopened, there is no undefined behaviour here (even prior to C++17) – M.M Jul 23 '18 at 22:52
  • It's a valid question, but why are you writing code like this? It's far too difficult to read and understand and (as a result) is behaving in unexpected ways (even if the compiler is correct, which I have to assume it is). You can write the exact same logic in _much_ cleaner ways without sacrificing performance. So why not make it readable? – JMAA Jul 23 '18 at 23:07
  • @JMAA it could be an exercise to help with understanding of the language rules – M.M Jul 23 '18 at 23:08
  • @M.M: Since `g` modifies the second argument, the `y` variable is modified and its value is used within the same expression. Before C++11 that was always Undefined Behavior. C++11 introduced a sequenced-before for the update action of assignment, and C++17 introduced sequenced-before for argument evaluation for built-in `<<`, §8.8/4. – Cheers and hth. - Alf Jul 23 '18 at 23:28
  • @Cheersandhth.-Alf it was not undefined before C++11; see C++03 1.9/17: "When calling a function (whether or not the function is inline), there is a sequence point after the evaluation of all function arguments (if any) which takes place before execution of any expressions or statements in the function body. There is also a sequence point after the copying of a returned value and before the execution of any expressions outside the function" – M.M Jul 23 '18 at 23:32
  • @M.M. Consider what you think would guaranteed come after or before the function call `f(x)` in the expression `a(x) + f(x) + b(x)`. There is no possibility that is consistent with your idea of well-defined sequence of evaluations for `x`. In particular, `a(x)` can be evaluated in full after `f(x)`. Here I use `+` instead of `<<` deliberately, since the latter got some sequencing support in C++17. The text that you quote means that the arguments of the call to `f` are fully evaluated when the `f` body executes. It says nothing about the evaluation order in the expression that that call is in. – Cheers and hth. - Alf Jul 23 '18 at 23:40
  • @Cheersandhth.-Alf the functions calls can occur in any order, but the statements within one function can't be interleaved with statements in another function. It can be unspecified in which order execution reaches two particular sequence points . Another example of that would be `f( a() || b(), c() || d() )`. If `b` and `d` are called then it's unspecified which order they are called, however there is still a sequence point associated with each `||`, and if `d()` is called then `c()` must have been called first, etc. – M.M Jul 23 '18 at 23:43
  • Right, but that unspecified order was not my point. Rather, the point is that what goes on inside functions called in an expression (the internal-to-the-calls sequence points you mentioned) has nothing to do with the order of evaluation of arguments in the expression. Or in particular, with the Undefined Behavior of separately modifying an object and using its value in the same expression. – Cheers and hth. - Alf Jul 23 '18 at 23:47
  • @Cheersandhth.-Alf there is no rule "Undefined Behavior of separately modifying an object and using its value in the same expression.", e.g. `i++, i++` is a counterexample. The rule (in C++03) was that there cannot be modification and using the value without an intervening sequence point. There is a sequence point between any expression inside a function, and any expression outside that function. – M.M Jul 23 '18 at 23:57
  • Also see C++03 1.9/8 "Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed." – M.M Jul 24 '18 at 00:12

1 Answers1

0

Prior to C++17, in the line:

cout << g(g(x, y), y) << ' ' << x << ' ' << y << endl;

the value stored in x and y for the latter part of the expression could each either be read before or after or in between the calls to g.

Note that the expressions y in the argument list for g do not read the stored value: y is an lvalue being bound directly to an lvalue reference function parameter, so there is no lvalue-to-rvalue conversion.

The calls to g have the following behaviour, where x and y refer to the variables in main:

  • Initial: x = 5, y = 2.
  • After inner call to g: x = 5, y = 7 (call returns 13).
  • After outer call to g: x = 5, y = 20 (call returns 34).

So the output will start with 34 5, but the last number could either be 2, 7 or 20. This is known as unspecified behaviour.

Since C++17, the operands of a << chain are sequenced from left to right; the only possible output now is 34 5 20.


Note: Some comments claimed there is undefined behaviour, however there is not. In C++03 terminology, there is a sequence point on entry and exit of a function call; the modification of y in the function is separated from the read of y in main by one of those sequence points. In C++11 the sequencing is the same but the terminology changed. See point 11 here.

M.M
  • 138,810
  • 21
  • 208
  • 365