4

Look at this simple function call:

f(a(), b());

According to the standard, call order of a() and b() is unspecified. C++17 has the additional rule which doesn't allow a() and b() to be interleaved. Before C++17, there was no such rule, as far as I know.

Now, look at this simple code:

int v = 0;

int fn() {
    int t = v+1;
    v = t;
    return 0;
}

void foo(int, int) { }

int main() {
    foo(fn(), fn());
}

With C++17 rules, v surely will have the value of 2 after the call of foo. But, it makes me wonder, with pre-C++17, is the same guaranteed? Or could it be that v ends up 1? Does it make a difference, if instead of int t = v+1; v = t;, we just have v++?

P.W
  • 26,289
  • 6
  • 39
  • 76
geza
  • 28,403
  • 6
  • 61
  • 135
  • 5
    Calls to functions where allowed to interleave in no version of C++. – n. m. could be an AI May 24 '19 at 11:42
  • 1
    https://en.wikipedia.org/wiki/Sequence_point <- before C++11 the relevant concept was sequence points –  May 24 '19 at 11:45
  • Related: [What are the evaluation order guarantees introduced by C++17?](https://stackoverflow.com/questions/38501587/what-are-the-evaluation-order-guarantees-introduced-by-c17); Although not as specific; – TrebledJ May 24 '19 at 11:45
  • @n.m.: Seems logical. But why is http://eel.is/c++draft/intro.execution#11 was expanded with "no-interleave allowed" in C++17? – geza May 24 '19 at 11:50
  • @n.m.: Oops, I've made a mistake here, I overlooked this somehow in pre-C++17. – geza May 24 '19 at 11:53
  • Any version of the C++ std or any C++ variant that allows such interleaving would be essentially useless for imperative programming so not capable of supporting many OOP idioms. – curiousguy May 24 '19 at 21:34
  • This is a fine reproducible non-typo question. Why VTC as "a problem that can no longer be reproduced or a simple typographical error?" – L. F. Jun 02 '19 at 01:57

4 Answers4

6

Functions calls were not allowed to interleave in previous versions as well.

Quoting from C++11 final draft (n3337)

1.9 Program execution [intro.execution]
...

15. ... When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [ Note: Value computations and side effects associated with different argument expressions are unsequenced. —end note ] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function9.


9) In other words, function executions do not interleave with each other.

Similar wording can be found in the final draft of the C++14 version as well.

P.W
  • 26,289
  • 6
  • 39
  • 76
  • 3
    The OP is confusing functions with *expressions*, which could interleave in C++14. That is if you have a compound expression like `f(a() + b(), c() + d())`, the order of execution could have been `a()`, then `c()`, then `b()`, then `d()`, etc. – Nicol Bolas May 24 '19 at 13:29
2

v must be 2 in all versions of C++ (and C). The function fn() must be executed twice, and clearly each time it executes it will increment v. There is no multithreading here, no data race, and no possibility for fn() to be executed only partially and then interrupted while the other invocation of fn() proceeds.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
2

C++17 has the additional rule which doesn't allow a() and b() to be interleaved. Before C++17, there was no such rule, as far as I know.

There were rules that applied here, though the wording and some details have grown more exact.

C++03 [intro.execution]/8:

Once the execution of a function begins, no expressions from the calling function are evaluated until execution of the called function has completed. [footnote 8]

[footnote 8]: In other words, function executions do not interleave with each other.

Though you could argue this doesn't actually say anything about other functions called from the calling function in the text, and footnotes are officially not normative.

C++11 changed the wording, largely because it introduced multithreading semantics. C++11 and C++14 [intro.execution]/15, emphasis mine:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. [Note: Value computations and side effects associated with different argument expressions are unsequenced. - end note] Every evaluation in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the execution of the body of the called function is indeterminately sequenced with respect to the execution of the called function. [footnote 9]

[footnote 9] In other words, function executions do not interleave with each other.

This wording, I think, leaves no doubt, at least in most cases.

C++17 [intro.execution]/18:

When calling a function (whether or not the function is inline), every value computation and side effect associated with any argument expression, or with the postfix expression designating the called function, is sequenced before execution of every expression or statement in the body of the called function. For each function invocation F, for every evaluation A that occurs within F and every evaluation B that does not occur within F but is evaluated on the same thread and as part of the same signal handler (if any), either A is sequenced before B or B is sequenced before A. [footnote 10] [Note: If A and B would not otherwise be sequenced then they are indeterminately sequenced. - end note]

[footnote 10] In other words, function executions do not interleave with each other.

This is a much more general statement about all evaluations in separate functions, not just arguments in a function call. But as far as I know, this more precise wording just clarifies some possibly ambiguous cases, but doesn't really change semantic behavior much.

Community
  • 1
  • 1
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

I suspect we are confusing two concepts. Call order was only fixed within expressions in c++17. Interleaving of the statements in a function between 2 calls of functions has always been disallowed, though when the optimiser gets hold of your code, only the effect is guaranteed.

If you write a() ; b() then a() will be fully executed before b is then fully executed. Same if you write a(), b() or a() && b()

If you write a() + b() then there is no guarantee prior to c++17 that a() will be executed before b(), but there is a guarantee that whichever is executed first will be fully completed before the other is executed.

If you write c(a(), b()) it [is my understanding] that again there is no guarantee that a() will be executed before b(), but there /is/ the guarantee that the effects of the first executed function (whichever that is) will complete before the second is started, and of course there is the guarantee that c() will not be executed until both a() and b() have completed.

The optimiser should honour the intent of that guarantee, though it may take the statements of a() and b(), and even c(), and intermingle them, the effect of running the code should be the same as if they were run in sequence. Could the -O0 code do a() then b() and the -O3 code do b() then a()? Perhaps, though that would make the debugging quite difficult, so I would hope not.

The compiler only has to give the effect in the final result, and it is possible in multithreading for another thread to observe the effects of consecutive lines of code occur out of order unless specific thread-aware constructs are used.

It is my understanding that the optimiser can't allow the specific effects of a(), b(), and c() to appear to occur out of order between each function, though prior to c++17 the order of a() and b() is not well defined - all effects of b() might occur before all effects of a().

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27