在GNU编译器中发现错误更高版本?

问题描述

我发现该代码在优化时会导致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;
}

要使用g++ -Os fail.cpp进行编译,可执行文件不会打印七个数字,它会一直持续打印和打印。我正在使用-

-rwxr-xr-x 4 root root 700388 Jun  3  2013 /usr/bin/g++

是否有更高版本的更正版本?

解决方法

编译器非常非常很少出错。在这种情况下,b正在溢出,which is undefined behaviour for signed integers

$ 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)
      |                       ~^~

如果您调用未定义的行为,则编译器可以自由执行其喜欢的任何事情,包括使程序永不终止。


编辑:有些人似乎认为UB应该只影响b的值,而不影响循环迭代。这不符合标准(UB可能导致任何事情发生),但是这是一个合理的想法,所以让我们看一下生成的程序集,看看循环为什么不会终止。

第一个without -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

然后with -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

比较和跳转指令完全消失了。具有讽刺意味的是,编译器完全按照您的要求进行了操作:优化大小,因此在遵循C ++标准的同时尽可能多地删除指令。 -O3-O2在这里生成与-Os完全相同的代码。

-O1 generates的输出非常有趣:

.LC0:
        .string "%d  "
main:
        push    rbx
        mov     ebx,0
.L2:
        add     ebx,333666999
        mov     esi,ebx
        mov     edi,0
        call    printf
        cmp     ebx,-1959298303
        jne     .L2
        mov     eax,9
        pop     rbx
        ret

在这里,编译器优化了循环计数器i并仅将b的值与7次迭代后的最终值进行比较,利用有符号溢出根据两位补码发生的事实在这个平台上!厚脸皮,不是吗? :)

,

我正在使用g ++版本4.8.1。 Thomas的版本为10.2.0,当添加两个有符号整数时,显然会发出有关“未定义行为”的警告。但是,仅警告仍会继续并编译程序。但是,在所有情况下,“未定义行为”都只应与要添加的整数有关。实际上,这些整数实际上遵守2的补码期望结果。 “未定义的行为”不应覆盖程序中的其他变量。否则,该可执行文件将根本无法被信任。如果无法信任它,则不应对其进行编译。也许还有gnu编译器的更高版本可以在优化时正常工作?