Are function arguments not evaluated left to right? How are side effects on the same variable ordered?
Asked Answered
C

1

1

Consider the following program.

#include<iostream> 
using namespace std; 

void fn(int a, int b)
{
    cout << a;
    cout << b;
}

int main()
{
    int a = 10;
    fn(a++, --a);
    fn(a--, ++a);
    return 0;
}

I don't understand the output I get (gcc 11.2):

9101110

Shouldn't a++ be evaluated first? How can fn then get a 9? Is this undefined behavior or simply "indeterminate"? Did C++17 change in this respect?

Connect answered 6/2, 2022 at 8:59 Comment(6)
Does this answer your question? Compilers and argument order of evaluation in C++Extrovert
Your program does not compile: iostream is not included, namespace std is not used, and fn is not defined when it is called in main. Additionally, the output you show is not the output the code shown would produce (no spaces are output between the numbers). What you show us is not what you execute. Admittedly the errors are easy to fix but suddenly we must guess how your program really looks which makes answering a guess as well, and cumbersome, too.Rocker
I can't get the answer by the way...Connect
This still does not compile. Why don't you simply copy and paste your working program here!? Do not manually edit anything in the edit field here -- only copy and paste from your editor.Rocker
@πάντα ῥεῖ This is not a dup with the linked question, at least not in C++20. In 7.6.1.3/8, the standard says: "The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter." It is not unsequenced. Unsequenced would be UB per 6.9.1/10, but indeterminately simply says it's sequenced but we don't know how.Rocker
I edited the question to (1) make the code compile (finally! ;-) ), (2) point to the relevant tags, (3) address the fact (I think) that the language changed in 2017, and (4) asked more concrete questions about the evaluation order that probably inspired your asking here. If I misrepresented you feel free to edit any way you like. And I also think the question was closed wrongly, or at least the link provided doesn't answer it ("expression lists" -- the a++, --a in fn(a++, --a)-- are not expressions).Rocker
W
-2

This is undefined behavior. The order of parameter evaluation is unspecified.

See here.

So this question is irrelevant and code like this should never be used.

The output can be 9 10 11 10 on one compiler, and a totally different value on another compiler (while both compilers remain standard-complaint).

Wheelbarrow answered 6/2, 2022 at 9:11 Comment(20)
It's sad how often I've seen this on C/C++ tests in schools and even universities.Wheelbarrow
This question relevance notwithstanding (I find it near-impossible to believe there isn't at least one duplicate somewhere; surely there is), per the language standard the order of function argument evaluation is unspecified ; it is not undefined.Sharice
Thanks, you are correct and i have corrected the term. The down vote might have been due to me being a bit harsh with the relevance :)Wheelbarrow
@Peter-ReinstateMonica I don't think there's even any UB in this; it's just a really bad idea to rely on because of the unspecified eval order. Each argument will be evaluated, the value reaped for eventually passing. The unspecified order is what makes this such a terrible thing to do. A fun (and a bit heated) exchange on a similar question (for completely different inquisitive reasons) can be seen here.Sharice
@Sharice Of course it is UB -- " If a side effect on a scalar object is un-sequenced relative to another side effect on the same scalar object, the behavior is undefined"Rocker
Yeah, I know, but i thought C++17 changed that, specifically for function arguments. I admit, haven't been on my keeping-up-with-the-standards, but I was under the impression that was specifically addressed in 17. If you know to the contrary, i'll certainly sit corrected.Sharice
@Sharice Here is a nice explanation: Since the instructions for both side effects can even be interleaved there are possible unexpected outcomes.Rocker
@Sharice The C++17 change is unknown to me, I spoke in haste and with too much confidence.Rocker
@Sharice This answer discussing C++17 proposals seems to indicate that it was assignment which was specifically targeted (and explicitly intuitively sequenced right-to-left) but not other subexpressions. I would have to look it up in the standard but I don't have it here on this 'puter.Rocker
@Sharice so what's the final conclusion about my question i mean how first cout is 9 can you explain it hereConnect
@AadilKhan The answer is no, I can't. Only the implementation authors could.Sharice
@Sharice So my standard says (7.6.1.3/8) about the expression-list (of a function): "The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter." That is not unsequenced, which would be UB if "a side effect on a memory location is unsequenced relative to either another side effect on the same memory location or a value computation using the value of any object in the same memory location ..." (6.9.1/10). I must assume that such side effects in arguments are not UB.Rocker
@AadilKhan Well, to quote the standard (via the publicly accessible draft): "The initialization of a parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other parameter." It is unclear which expression (in your case: a++ or --a in the first call of fn) is evaluated first. Here, --a appears to have been evaluated first, changing a's value to 9 which was then used to initialize fn's a. The more interesting question to me is why fn's b is 10.Rocker
@Peter-ReinstateMonica: It’s the initialization of the parameters that is indeterminately sequenced, not the evaluation of the argument expressions.Bloomsbury
@Davis Oh. I read that as synonymous, or at least as the argument evaluation being a first step -- one of the "associated value computations" -- of the initialization. You suggest initialization here means execution of constructors etc. with respect to possible side effects on other parameter initializations? I'm not sure how to separate the value computation of a constructor (or simple initialization) argument from the actual initialization.Rocker
@Peter-ReinstateMonica: Yes, initialization includes constructors (and/or conversion functions) needed to convert the final calculated value of an argument (during which its side effects have already occurred and including any functions it calls). The statement about its value computations is saying that a read of a variable inside such a constructor can't see a value that exists only during another such constructor call.Bloomsbury
@DavisHerring I asked a separate question about function call arguments and was pointed to the standard proposal making parameter initialization indeterminately sequenced here. The intent is clearly that specifically the evaluation of the arguments is sequenced ("Every value computation and side effect associated with the initialization of a parameter, and the initialization itself...").Rocker
@Peter-ReinstateMonica: You have to be careful reading proposals: that one was adopted with the “Alternate Evaluation Order…” (marked with “not to pursue”) applied, and my interpretation is that the wording applied did not reflect the “evaluations of the arguments are indeterminately sequenced” (emphasis added) from the prose.Bloomsbury
@DavisHerring The statement about its value computations is saying that a read of a variable inside such a constructor can't see a value that exists only during another such constructor call What was the point in specifying this for constructors/conversion functions invocations in C++17 parameters initialization when any function invocations, anywhere, couldn't «interleave» since forever?Johore
@LanguageLawyer: I said "constructors" because that was the going subject of conversation; it also applies to conversions requried for those constructors' arguments (which are grouped together here even if they might be several function calls), aggregate initialization effects, etc. Obviously there is uncertainty as to what that phrasing means, but that's how I've been interpreting it.Bloomsbury

© 2022 - 2024 — McMap. All rights reserved.