在 GNU 编译器中发现错误。以后的版本?

Cli*_*ive 1 c++ gnu fault

我发现这段代码在优化时会在 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)

以后有更正的版本吗?

Tho*_*mas 8

编译器很少出错。在这种情况下,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)

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