gcc/g++ 和 clang:条件优化错误
gcc/g++ and clang: wrong optimization of conditional
我有一个关于 gcc 和 clang 代码优化的问题。这段代码表现出奇怪的行为。 arr
在 main
中初始化为 0
,在 arr_ctor
中变为 sizeof(int)
,在 arr_resize
中变为 0
。所以,不应该执行条件。当使用 -O2 条件编译时,条件被消除并执行 fprintf
。但是,当使用带有 /O2 条件的 MSVC 时,代码会正常工作。
#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;
}
命令行:
gcc main.cpp -o test_gcc -O2 -Wall -Wextra
clang main.cpp -o test_clang -O2 -Wall -Wextra
输出 (gcc):
arr = (nil)
输出(clang):
arr = (nil)
输出(MSVC):无输出
程序集显示条件语句在 GCC 和 Clang 中被删除,但在 MSVC 中出现。
海湾合作委员会(-O2):
<...>
arr_resize:
subq , %rsp
leaq -4(%rdi), %rdx
movq stderr(%rip), %rdi
xorl %eax, %eax
leaq .LC0(%rip), %rsi
call fprintf@PLT
xorl %eax, %eax
addq , %rsp
ret
<...>
叮当声 (-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
<...>
命令行:
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 仅在带有 /O2 的 Godbolt 上进行了测试,因为我没有。 Clang 和 GCC 在 Godbolt 和我的 PC 上进行了测试。
相比之下,没有优化的 GCC:
<...>
arr_resize:
.LFB0:
pushq %rbp
movq %rsp, %rbp
subq , %rsp
movq %rdi, -8(%rbp)
subq , -8(%rbp)
cmpq [=15=], -8(%rbp)
je .L2
movq stderr(%rip), %rax
movq -8(%rbp), %rdx
leaq .LC0(%rip), %rcx
movq %rcx, %rsi
movq %rax, %rdi
movl [=15=], %eax
call fprintf@PLT
.L2:
movl [=15=], %eax
leave
ret
<...>
编译器:
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
这是一个错误还是我遗漏了什么?
您问了 2 个问题
- 为什么代码被淘汰了
- 为什么执行printf
它被淘汰是因为指针的算术永远不会产生 nullptr。所以它被视为
if(42 == 42) // ie always true
NULL 的算术是 UB,一旦你这样做了,所有的赌注都关闭了。 printf 可能会发生,也可能不会
我有一个关于 gcc 和 clang 代码优化的问题。这段代码表现出奇怪的行为。 arr
在 main
中初始化为 0
,在 arr_ctor
中变为 sizeof(int)
,在 arr_resize
中变为 0
。所以,不应该执行条件。当使用 -O2 条件编译时,条件被消除并执行 fprintf
。但是,当使用带有 /O2 条件的 MSVC 时,代码会正常工作。
#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;
}
命令行:
gcc main.cpp -o test_gcc -O2 -Wall -Wextra
clang main.cpp -o test_clang -O2 -Wall -Wextra
输出 (gcc):
arr = (nil)
输出(clang):
arr = (nil)
输出(MSVC):无输出
程序集显示条件语句在 GCC 和 Clang 中被删除,但在 MSVC 中出现。
海湾合作委员会(-O2):
<...>
arr_resize:
subq , %rsp
leaq -4(%rdi), %rdx
movq stderr(%rip), %rdi
xorl %eax, %eax
leaq .LC0(%rip), %rsi
call fprintf@PLT
xorl %eax, %eax
addq , %rsp
ret
<...>
叮当声 (-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
<...>
命令行:
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 仅在带有 /O2 的 Godbolt 上进行了测试,因为我没有。 Clang 和 GCC 在 Godbolt 和我的 PC 上进行了测试。
相比之下,没有优化的 GCC:
<...>
arr_resize:
.LFB0:
pushq %rbp
movq %rsp, %rbp
subq , %rsp
movq %rdi, -8(%rbp)
subq , -8(%rbp)
cmpq [=15=], -8(%rbp)
je .L2
movq stderr(%rip), %rax
movq -8(%rbp), %rdx
leaq .LC0(%rip), %rcx
movq %rcx, %rsi
movq %rax, %rdi
movl [=15=], %eax
call fprintf@PLT
.L2:
movl [=15=], %eax
leave
ret
<...>
编译器:
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
这是一个错误还是我遗漏了什么?
您问了 2 个问题
- 为什么代码被淘汰了
- 为什么执行printf
它被淘汰是因为指针的算术永远不会产生 nullptr。所以它被视为
if(42 == 42) // ie always true
NULL 的算术是 UB,一旦你这样做了,所有的赌注都关闭了。 printf 可能会发生,也可能不会