Wednesday, January 5, 2022

[SOLVED] The short circuit evaluation in C is not reflected by compiler

Issue

I am trying to compile the below C code with -O3 optimization flag by GCC and Clang, and after looking at the generated assembly code, I find neither of these compilers implements the short circuit evaluation which is mentioned in the C standard for the && operator.

You can refer to the below assembly code for further information, the first five lines of code of the foo function would be run sequentially, and it would compare both two operands of the && operators which actually violates the standard. So, any misunderstandings here?

C code:

#include <stdio.h>
#include <stdbool.h>
void foo(int x, int y) {
  bool logical = x && y;
  printf("%d", logical);
}
int main(void) {
  foo(1, 3);
  return 0;
}

Generated assembly code:

foo:                                    # @foo
        test    edi, edi
        setne   al
        test    esi, esi
        setne   cl
        and     cl, al
        movzx   esi, cl
        mov     edi, offset .L.str
        xor     eax, eax
        jmp     printf                          # TAILCALL
main:                                   # @main
        push    rax
        mov     edi, offset .L.str
        mov     esi, 1
        xor     eax, eax
        call    printf
        xor     eax, eax
        pop     rcx
        ret
.L.str:
        .asciz  "%d"

Solution

First, your example is faulty.

Given x = 1 and y = 3, evaluating x && y requires evaluating both operands, since the && is only true if both operands are true.

Second, C 2018 5.1.2.3 6 says:

The least requirements on a conforming implementation are:

— Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.

— At program termination, all data written into files shall be identical to the result that execution of the program according to the abstract semantics would have produced.

— The input and output dynamics of interactive devices shall take place as specified in 7.21.3. The intent of these requirements is that unbuffered or line-buffered output appear as soon as possible, to ensure that prompting messages actually appear prior to a program waiting for input.

This is the observable behavior of the program.

This means the compiler is only responsible for ensuring the above behaviors occur as specified. It is not required to generate assembly language that does not evaluate the right operand of && or || when the result can be deduced from the left operand as long as that evaluation does not alter the observable behaviors. The compiler has no obligation to generate the assembly language you seek, just to ensure the observable behaviors are as specified.

When the standard describes expressions as being evaluated or not, it is describing them in the context of an “abstract machine” (C 2018 5.1.2.3 1), not describing what the generated program must actually do on the assembly language level. The idea is that the C standard describes what a program would generate in an abstract machine, and then the compiler may generate any program that has the same observable behavior as the program in an abstract machine, even if it obtains that result in a completely different way than the program in the abstract machine does.



Answered By - Eric Postpischil