'gcc/g++ and clang: wrong optimization of conditional
I have a question about gcc and clang code optimization. This piece of code shows strange behavior. arr initialized as 0 in main, becomes sizeof(int) in arr_ctor and becomes 0 in arr_resize. So, conditional shouldn't be executed. When compiled with -O2 conditional gets eliminated and fprintf executes. However, when using MSVC with /O2 conditional stays presented and code works fine.
#include <stdio.h>
int arr_resize(int* arr)
{
arr--;
if(arr != nullptr) // this conditional shouldn't be removed
fprintf(stderr, "arr = %p\n", arr); //
return 0;
}
int arr_ctor(int* arr)
{
arr++;
arr_resize(arr);
return 0;
}
int main()
{
int* arr = {};
arr_ctor(arr);
return 0;
}
Command line:
gcc main.cpp -o test_gcc -O2 -Wall -Wextra
clang main.cpp -o test_clang -O2 -Wall -Wextra
Output (gcc):
arr = (nil)
Output (clang):
arr = (nil)
Output (MSVC): no output
Assembly shows that conditional was eliminated in GCC and Clang, but presented in MSVC.
GCC (-O2):
<...>
arr_resize:
subq $8, %rsp
leaq -4(%rdi), %rdx
movq stderr(%rip), %rdi
xorl %eax, %eax
leaq .LC0(%rip), %rsi
call fprintf@PLT
xorl %eax, %eax
addq $8, %rsp
ret
<...>
Clang (-O2):
<...>
arr_resize:
pushq %rax
leaq -4(%rdi), %rdx
movq stderr@GOTPCREL(%rip), %rax
movq (%rax), %rdi
leaq .L.str(%rip), %rsi
xorl %eax, %eax
callq fprintf@PLT
xorl %eax, %eax
popq %rcx
retq
<...>
MSVC (/O2):
<...>
int arr_resize(int *)
push rbx
sub rsp, 32
mov rbx, rcx
sub rbx, 4
je SHORT $LN4@arr_resize
mov ecx, 2
call __acrt_iob_func
mov rcx, rax
lea rdx, OFFSET FLAT:`string'
mov r8, rbx
call fprintf
$LN4@arr_resize:
xor eax, eax
add rsp, 32
pop rbx
ret 0
<...>
Command line:
gcc main.cpp -o test.s -S -O2 -Wall -Wextra -fno-exceptions
clang main.cpp -o test.s -S -O2 -Wall -Wextra -fno-exceptions
MSVC was tested only on godbolt with /O2, because I don't have it. Clang and GCC were tested on godbolt and on my PC.
For comparison, GCC without optimizations:
<...>
arr_resize:
.LFB0:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
subq $4, -8(%rbp)
cmpq $0, -8(%rbp)
je .L2
movq stderr(%rip), %rax
movq -8(%rbp), %rdx
leaq .LC0(%rip), %rcx
movq %rcx, %rsi
movq %rax, %rdi
movl $0, %eax
call fprintf@PLT
.L2:
movl $0, %eax
leave
ret
<...>
Compilators:
gcc version 11.2.0 (11.2.0 on godbolt)
clang version 13.0.1 (14.0.0 on godbolt)
MSVC version 19.31 on godbolt
Is it a bug or am I missing something?
Solution 1:[1]
You are asking 2 questions
- why does the code get eliminated
- why is that printf executed
It gets eliminated becuase arithmetic on a pointer can never yield nullptr. So it gets treated as
if(42 == 42) // ie always true
Arithmetic on NULL is UB, once you do that all bets are off. printf might happen, might not
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | pm100 |
