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



我有一个关于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 = %pn", 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)

输出(叮当声):

arr = (nil)

输出(MSVC):无输出

汇编显示条件在GCC和Clang中被取消,但在MSVC中出现。

GCC (- 02):

<...>
arr_resize:
    subq    $8, %rsp
    leaq    -4(%rdi), %rdx
    movq    stderr(%rip), %rdi
    xorl    %eax, %eax
    leaq    .LC0(%rip), %rsi
    call    fprintf@PLT
    xorl    %eax, %eax
    addq    $8, %rsp
    ret
<...>

叮当声(- 02):

<...>
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只在godbolt上用/O2测试过,因为我没有。Clang和GCC分别在godbolt和我的PC上进行了测试。

比较一下,没有优化的GCC:

<...>    
arr_resize:
.LFB0:
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    subq    $4, -8(%rbp)
    cmpq    $0, -8(%rbp)
    je  .L2
    movq    stderr(%rip), %rax
    movq    -8(%rbp), %rdx
    leaq    .LC0(%rip), %rcx
    movq    %rcx, %rsi
    movq    %rax, %rdi
    movl    $0, %eax
    call    fprintf@PLT
.L2:
    movl    $0, %eax
    leave
    ret
<...>

Compilators:

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

这是一个bug还是我错过了什么?

你问了两个问题

  • 为什么代码被淘汰
  • 为什么要执行printf

它被消除了,因为指针上的算术运算永远不会产生nullptr。所以它被当作

if(42 == 42) // ie always true

NULL上的算术是UB,一旦你这样做,所有的赌注都取消了。Printf可能发生,也可能不发生

最新更新