Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this code print 1 2 2 and not the expected 3 3 1? [duplicate]

Notice: this is a self-Q/A and a more visible targeting the erroneous information promoted by the book "Let us C". Also, please let's keep the c++ out of the discussion, this question is about C.

I am reading the book "Let us C" by Yashwant Kanetkar.

In the book there is the following example:

#include <stdio.h>

int main(void) {
    int a = 1;
    printf("%d %d %d", a, ++a, a++);
}

The author claims that this code should output 3 3 1:

Surprisingly, it outputs 3 3 1. This is because C’s calling convention is from right to left. That is, firstly 1 is passed through the expression a++ and then a is incremented to 2. Then result of ++a is passed. That is, a is incremented to 3 and then passed. Finally, latest value of a, i.e. 3, is passed. Thus in right to left order 1, 3, 3 get passed. Once printf( ) collects them it prints them in the order in which we have asked it to get them printed (and not the order in which they were passed). Thus 3 3 1 gets printed.

However when I compile the code and run it with clang, the result is 1 2 2, not 3 3 1; why is that?

like image 729

2 Answers

The author is wrong. Not only is the order of evaluation of function arguments unspecified in C, the evaluations are unsequenced with regards to each other. Adding to the injury, reading and modifying the same object without an intervening sequence point in independent expressions (here the value of a is evaluated in 3 independent expressions and modified in 2) has undefined behaviour, so the compiler has the liberty of producing any kind of code that it sees fit.

For details, see Why are these constructs using pre and post-increment undefined behavior?

like image 132
2 revs, 2 users 60% Avatar answered Dec 09 '22 09:12

2 revs, 2 users 60%


C’s calling convention

This has nothing to do with calling convention! And C does not even specify a certain calling convention - "cdecl" etc are x86 PC inventions (and have nothing to do with this). The correct and formal C language term is order of evaluation.

The order of evaluation is unspecified behavior (formally defined term), meaning that we can't know if it is left to right or right to left. The compiler need not document it and need not have a consistent order from case to case basis.

But there is a more severe problem yet here: the so-called unsequenced side-effects. C17 6.5/2 states:

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.

This text is quite hard to digest for normal humans. A rough, simplified translation from language-lawyer nerd language to plain English:

  • In case the binary operators1) used in the expression don't explicitly state the order that the operands are executed2) and,
  • a side-effect, such as changing the value, happens to a variable in the expression, and,
  • that same variable is used elsewhere in the same expression,

then the program is broken and might do anything.


1) Operators with 2 operands.
2) Most operators don't do this, only a few exceptions like || && , operators do so.

like image 40
Lundin Avatar answered Dec 09 '22 09:12

Lundin