Home > database >  The short circuit evaluation in C is not reflected by compiler
The short circuit evaluation in C is not reflected by compiler

Time:12-03

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"

CodePudding user response:

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.

CodePudding user response:

It is. But the compiler chooses the fastest way as requested.

  1. main - The compiler knows the result compile time and is not calling function foo at all
  2. foo - Branches are quite expensive operations as they require flushing the pipeline, potential cache miss, reading from slow memory etc. So it makes no sense to branch in this case. Your example is simply too trivial.

Consider a bit less trivial example:

bool bar(void);

void foo(int x) {
  bool logical = x && bar();
  printf("%d", logical);
}
int main(void) {
  foo(1);
  return 0;
}
foo:
        test    edi, edi
        jne     .L12
        mov     esi, edi
        xor     eax, eax
        mov     edi, OFFSET FLAT:.LC0
        jmp     printf
.L12:
        sub     rsp, 8
        call    bar
        mov     edi, OFFSET FLAT:.LC0
        add     rsp, 8
        movzx   esi, al
        xor     eax, eax
        jmp     printf
main:
        sub     rsp, 8
        mov     edi, 1
        call    foo
        xor     eax, eax
        add     rsp, 8
        ret

From C standard 6.5.13:

Unlike the bitwise binary & operator, the && operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares equal to 0, the second operand is not evaluated.

  • Related