-1

I would like to understand this code snippet as much as the undefined behavior permits it:

int i = 0;
printf("%d %d %d", i, ++i, i++);

output:

2 2 0

From what I can tell:

  • a comma , defines a sequence
  • the actual printing happens when all of the sequences are evaluated inside the function argument call
  • since arguments are pass-by-value, a copy happens sometime(?!) while calling the function
  • the order in which the function argument sequences evaluated is undefined ( is this true? )

So as far as I can tell most of the behavior in that single line of code is undefined, still I would like to understand the parts that are NOT undefined behavior. I know the output depends on the compiler, but what parts are there that are defined in the C Standard? I am interested in ANSI C, C99 as well, but I believe latest C++ standards improved on this at least in some aspects, is that true?

Lundin
  • 195,001
  • 40
  • 254
  • 396
Dávid Tóth
  • 2,788
  • 1
  • 21
  • 46
  • A comma that separates arguments only separates arguments. It does not define a sequence. – user253751 Aug 13 '21 at 08:19
  • is there a difference in behavior? https://stackoverflow.com/questions/52550/what-does-the-comma-operator-do – Dávid Tóth Aug 13 '21 at 08:21
  • 3
    @DavidTóth this is not an example of the comma operator. This is just an argument list. In fact I believe that unlike the comma operator, the arguments passed in the function can be evaluated in any order. – mediocrevegetable1 Aug 13 '21 at 08:22
  • thank you for that! I think what you said is part of the answer here. So this means that the arguments are actually in one sequence inside the printf? – Dávid Tóth Aug 13 '21 at 08:23
  • 1
    For reference, arguments evaluation order is unspecified in C. However, there is **specified and defined** behaviors regarding sequences since C++17 for C++. For C, it's mainly [still _unspecified/undefined_ in C](https://en.cppreference.com/w/c/language/eval_order). – Zilog80 Aug 13 '21 at 08:37

1 Answers1

2

a comma , defines a sequence

No. Don't confuse the comma operator (What does the comma operator , do?) for a function argument list. The actual , symbol is used in a lot of different places in the C syntax, but the only place where it has a well-defined order of evaluation is when it's used for the actual comma operator. Neither function call argument lists nor initializer lists have well-defined orders.

The C standard says this about function calls and arguments (C17 6.5.2.2):

There is a sequence point after the evaluations of the function designator and the actual arguments but before the actual call. 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.

I your case the 4 arguments "%d %d %d", i, ++i, i++ are indeterminately sequenced in relation to each other and the sequence point, as mentioned above, is placed after the evaluation of the arguments.


To address your other statements:

  • the actual printing happens when all of the sequences are evaluated inside the function argument call

    Correct, but then undefined behavior has already struck.

  • since arguments are pass-by-value, a copy happens sometime(?!) while calling the function

    You can assume that these are pass by value indeed. printf is an icky variadic function so they end up in a va_list, which I believe has an implementation-defined representation internally.

  • the order in which the function argument sequences evaluated is undefined ( is this true? )

    The order of evaluation of function arguments is unspecified behavior, meaning we can't know the order - it can be undocumented - and therefore we shouldn't assume a particular order. The actual undefined behavior happens because of 6.5/2:

    If a side effect on a scalar object is unsequenced relative to either a different side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined. If there are multiple allowable orderings of the subexpressions of an expression, the behavior is undefined if such an unsequenced side effect occurs in any of the orderings.

  • I would like to understand the parts that are NOT undefined behavior.

    I'm not sure what you mean with that, since code can't be "just a little bit undefined". If it has undefined behavior and then all bets are off regarding anything in that program. You can't reason about it or it wouldn't be undefined, but something else. Therefore it doesn't make sense to reason like "undefined behavior turns this value to 55 and then it is passed by value". For example maybe the UB causes the whole function call to be optimized away or get inlined in strange ways.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Thank you, that cleared everything up for me! Just to clarify: I wanted to confirm if my understanding is correct about this particular function call; I understand that the results depend on compile specific implementation, and undefined behavior, so I did not want to get into explaining the exact results. – Dávid Tóth Aug 13 '21 at 10:53
  • 1
    @DavidTóth An example of unspecified behavior (compiler-specific) which is not undefined behavior would be: `func(a(), b(), c());`. It is unspecified in which order the functions a, b and c are called, we can't assume the order, but we can assume that all 3 are called before func. – Lundin Aug 13 '21 at 11:29