cgcc

How do I determine the evaluation order of printf?


I have read many questions on this topic already:

Fairly unanimously, the conclusion is that "this is undefined/unspecified behavior." I do agree with that, and in any sensible program, I will try to avoid situations like these.

However, many universities (including mine) conduct pen-and-paper exams in which we need to evaluate these ambiguous statements. How do I proceed?

Can we at least narrow down on this behaviour for at least one compiler and architecture? In my case:

gcc version 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
Target: x86_64-linux-gnu

Running on Ubuntu 24.04 LTS (Noble Numbat) on a 12th generation Intel CPU (Alder Lake).


Example 1

#include <stdio.h>

int main() {
    int x;

    x = 20;
    printf("%d, %d\n", x++, x);

    x = 20;
    printf("%d, %d\n", x, x++);
}

Output:

20, 21
21, 20

I expected to get 20, 20 for the first one, but I clearly don't.


Example 2

#include <stdio.h>

int a() {
    printf("a has run!\n");
    return 7;
}

int b() {
    printf("b has run!\n");
    return 13;
}

int main() {
    printf("%d, %d\n", a(), b());
}

Output

b has run!
a has run!
7, 13

Solution

  • TL;DR:

    We cannot predict the results in either case, even when using the same compiler for the same target. A compiler is free to generate different results for unspecified behavior on case to case basis.


    Detailed answer:

    The order of evaluation of function parameters is unspecified behavior, meaning that some compilers will do left-to-right evaluation and others right-to-left (and in theory any order in between). Unspecified behavior means that one of multiple behaviors is implemented, but the compiler need not document which one and the compiler is free to do it differently from case to case.

    Can we at least narrow down on this behaviour for at least one compiler and architecture?

    No. Because the mainstream compilers do evaluate function parameters differently on case-to-case basis. The number of parameters, the type of the parameters, if optimizations are enabled, if the function is inlined etc etc. There are numerous different behaviors within the same compiler for the same target.

    Additionally, if there is at least one side effect such as writing to x in the same expression as another side effect or value computation, and there is an unspecified ("unsequenced") order in which the code is executed, the behavior is also undefined behavior. Meaning a bug, a potential crash, the whole program behaving wrong etc etc.

    So printf("%d, %d\n", x++, x); is an example where x++ and x are evaluated in an unspecified order, but they are therefore also unsequenced in relation to each other. The x++ is a side effect and it is unsequenced in relation to the value computation x, so the code is also undefined behavior.

    This is different from a situation like this:

    int x (void)
    {
      static int i=0;
      return i++;
    }
    
    printf("%d, %d\n", x(), x());`
    

    This is still unspecified behavior, so it can print either 0, 1 or 1, 0. But the function call comes with multiple sequence points, guaranteeing that all previous side effects (the i++) are carried out before next time that value is used. But it is not undefined behavior, so the code won't crash or produce completely unexpected results.