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

Viewed 314

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 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?

4 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?

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.

The author is wrong and the book has multiple instances of incorrect statements like this one.

In C, the behavior of printf("%d %d %d", a, ++a, a++); is undefined both because the order of evaluation of function arguments is unspecified and because modifying the same object multiple times between 2 sequence points has undefined behavior just to name these two.

Note that the book is referenced as do not use in The Definitive C Book Guide and List for providing incorrect advice with this precise example.

Note also that other languages may have a different take on this kind of statement, notably java where the behavior is fully defined.

I read many years ago in University (C step by step by Mitchell Waite) which c compiler (some compilers) uses stack for printf by pushing arguments from right to left (to specifier) and then pop them one by one and print them.

I write this code and the output is 3 3 1 : Online Demo.

Based on the book in the stack we have something like this

enter image description here

But after a minor challenges with experts (in comments) I found that maybe in some compilers this sequence be true but not for all.

@Lundin provided this code and the output is 1 2 2 :Online Demo

and @Bob__ provided another example which output is totally different: Online Demo

It totally depends on compiler implementation and has undefined behaviour.

Related