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.
main
- The compiler knows the result compile time and is not calling functionfoo
at allfoo
- 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.