gcc/g++ 和 clang:条件优化错误

gcc/g++ and clang: wrong optimization of conditional

我有一个关于 gcc 和 clang 代码优化的问题。这段代码表现出奇怪的行为。 arrmain 中初始化为 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 可能会发生,也可能不会