我发现这段代码在优化时会在 gnu C++ 编译器中导致一个惊人的错误。
#include <stdio.h>
int main()
{
int a = 333666999, b = 0;
for (short i = 0; i<7; ++i)
{
b += a;
printf("%d ", b);
}
return 9;
}
Run Code Online (Sandbox Code Playgroud)
使用g++ -Os fail.cpp可执行文件进行编译不会打印七个数字,它会永远继续,打印和打印。我在用 -
-rwxr-xr-x 4 root root 700388 Jun 3 2013 /usr/bin/g++
Run Code Online (Sandbox Code Playgroud)
以后有更正的版本吗?
编译器很少出错。在这种情况下,b溢出,这是有符号整数的未定义行为:
$ g++ --version
g++ (GCC) 10.2.0
...
$ g++ -Os -otest test.cpp
test.cpp: In function ‘int main()’:
test.cpp:8:11: warning: iteration 6 invokes undefined behavior [-Waggressive-loop-optimizations]
8 | b += a;
| ~~^~~~
test.cpp:6:24: note: within this loop
6 | for (short i = 0; i<7; ++i)
| ~^~
Run Code Online (Sandbox Code Playgroud)
如果您调用未定义的行为,编译器可以自由地做任何它喜欢的事情,包括使您的程序永不终止。
编辑:有些人似乎认为 UB 应该只影响 的值b,而不是循环迭代。这不符合标准(UB 可以导致任何事情发生),但这是一个合理的想法,所以让我们看看生成的程序集,看看为什么循环不会终止。
首先没有-Os:
.LC0:
.string "%d "
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-12], 333666999
mov DWORD PTR [rbp-4], 0
mov WORD PTR [rbp-6], 0
.L3:
cmp WORD PTR [rbp-6], 6 # Compare i to 6
jg .L2 # If greater, jump to end
mov eax, DWORD PTR [rbp-12]
add DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
movzx eax, WORD PTR [rbp-6]
add eax, 1
mov WORD PTR [rbp-6], ax
jmp .L3
.L2:
mov eax, 9
leave
ret
Run Code Online (Sandbox Code Playgroud)
然后用-Os:
.LC0:
.string "%d "
main:
push rbx
xor ebx, ebx
.L2:
add ebx, 333666999
mov edi, OFFSET FLAT:.LC0
xor eax, eax
mov esi, ebx
call printf
jmp .L2
Run Code Online (Sandbox Code Playgroud)
比较和跳转指令完全没有了。具有讽刺意味的是,编译器完全按照您的要求执行:优化大小,因此在遵守 C++ 标准的同时删除尽可能多的指令。-O3并-O2生成与-Os此处完全相同的代码。
-O1 生成一个非常有趣的输出:
.LC0:
.string "%d "
main:
push rbx
mov ebx, 0
.L2:
add ebx, 333666999
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
cmp ebx, -1959298303
jne .L2
mov eax, 9
pop rbx
ret
Run Code Online (Sandbox Code Playgroud)
在这里,编译器优化掉了循环计数器,i并b在 7 次迭代后仅将 的值与其最终值进行比较,使用这个平台上根据二进制补码发生有符号溢出的事实!厚脸皮不是吗?:)