0

Following are the statements of which I need to find out the output:

int k = 35;
printf("%d %d %d", k == 35, k = 50, k > 40);

Output of the above statements (in gcc compiler):

0 50 0

I ran the above code in VSCode with gcc compiler.

The output I am expecting is:

1 50 1

But, the actual Output is:

0 50 0

Interestingly, if I split the printf( ) statement as follows:

int k = 35;
printf("%d ", k == 35);
printf("%d ", k = 50);
printf("%d ", k > 40);

The actual output is as expected, that is:

1 50 1

So that's why I'm confused with the behavior of printf( ) statement in the original problem. Why the output is "0 50 0" and not "1 50 1" ?

Pranshul
  • 11
  • 3
  • 5
    It's not specified in which order arguments are evaluated. – Some programmer dude Dec 20 '22 at 10:48
  • @Someprogrammerdude So, do you mean this kind of unexpected behavior is a part of C ? or, is there anything I'm missing ? It would be really helpful if you can clarify more on that please. – Pranshul Dec 20 '22 at 10:54
  • 1
    Yes, it's kind of expected that some things will result in unexpected behavior. Usually it's because of *undefined behavior*, but unspecified or implementation defined behavior could come up with some surprises as well. See e.g. [Undefined, unspecified and implementation-defined behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) – Some programmer dude Dec 20 '22 at 10:58
  • The order is undefined because having free ordering enables lots of compiler optimizations. Specifically, different calling conventions may push the arguments on the stack in different orders. Saying that "the order is X or Y" would make some of the conventions impractically expensive. If you make your own `void(int,int,int)` function, you can experiment with different calling conventions and find out if that changes anything: https://stackoverflow.com/questions/297654/what-is-stdcall – Agent_L Dec 20 '22 at 11:12
  • Although this code does not use `++` or `--`, its output — or rather, its lack of predictable output — is explained by the answers at [Why are these constructs using pre and post-increment undefined behavior?](https://stackoverflow.com/questions/949433). – Steve Summit Dec 20 '22 at 12:03
  • You said you "need to find out the output". Does that mean this was an exercise given to you in a programming class? If so, your instructor is very badly misguided, and is teaching you a language other than C. There are probably other false lessons you have learned from this instructor also. – Steve Summit Dec 20 '22 at 12:09
  • The moral of the story is that if you want to explore the behavior of several C expressions where the order matters, like this, you want to use separate *statements*, separated by semicolons. The order of evaluation of statements is guaranteed: one after the other. As we've seen here, the order of evaluation of function arguments is not guaranteed. That's why, when you split the code into three separate `printf` calls in three separate statements, you saw the expected results. – Steve Summit Dec 20 '22 at 12:26
  • @SteveSummit Yes, it was given as an exercise, but thankfully not by my instructor, he is really a knowledgeable person. I found it in a book. – Pranshul Dec 20 '22 at 12:34

1 Answers1

4

The C standard does not specify any requirements for the behavior of this program. It may print “1 50 1” or “0 50 0”, it may print other outputs, it may abort, or it may do other things.

The argument k = 50 changes the value of k as a side effect. (In an assignment expression, a side effect is to change the value of the left operand. The main effect is that the expression has the new value of the left operand.) The arguments k == 35 and k > 40 compute values using the value of k.

The clause of the C standard that specifies function call behavior, 6.5.2.2, does not specify any sequencing between the arguments of a function call. Then another clause, C 2018 6.5 2, says:

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…

So, this printf call has a side effect on k, which is a scalar object, that is unsequenced relative to value computations using the value of k. So the behavior of the program is not defined by the C standard.

An underlying reason this behavior is not defined by the C standard is that we want compilers to optimize the performance of programs, and this may involve evaluating expressions in parts and executing those parts in any order. For example, if several arguments to a function have a common subexpression, we generally want the compiler to evaluate the common subexpression and use its value multiple times in the several arguments instead of recomputing it each time. So we want to allow the compiler to mix executions of multiple expressions if that improves program performance. It is up to authors of C programs to understand these rules and avoid mixing expressions that can interfere with each other in undesired ways.

Another aspect of this is that some expressions that look like single operations, such as k = 50, might not be. A compiler might support 64-bit integer arithmetic on computer hardware that has only 32-bit operations by using multiple instructions to perform the 64-bit operations. For example, it might perform a 64-bit add by adding the low 32 bits of two numbers, storing the low 32 bits into the result, adding the high 32 bits of the numbers and a carry from the low 32 bits, and storing the high 32 bits into the result. When you combine this fact with the mixing of expression evaluations, it is possible that some parts of an expression like k > 40 (perhaps comparing the high bits) are evaluated before some parts of k = 50 (perhaps storing the low bits) while other parts are evaluated after them. So this means that, when you have arguments like k == a, k = b, k > c, the evaluation of the parts in different orders might produce a result that would actually be impossible if each argument were fully evaluated by itself in any of the six possible orders of three separate things.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312