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

I have a question about gcc and clang code optimization. This piece of code shows strange behavior. arr initialized as 0 in main, becomes sizeof(int) in arr_ctor and becomes 0 in arr_resize. So, conditional shouldn't be executed. When compiled with -O2 conditional gets eliminated and fprintf executes. However, when using MSVC with /O2 conditional stays presented and code works fine.

#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;
}

Command line:

gcc main.cpp -o test_gcc -O2 -Wall -Wextra

clang main.cpp -o test_clang -O2 -Wall -Wextra

Output (gcc):

arr = (nil)

Output (clang):

arr = (nil)

Output (MSVC): no output

Assembly shows that conditional was eliminated in GCC and Clang, but presented in MSVC.

GCC (-O2):

<...>

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

<...>

Clang (-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

<...>

Command line:

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 was tested only on godbolt with /O2, because I don't have it. Clang and GCC were tested on godbolt and on my PC.

For comparison, GCC without optimizations:

<...>    

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

Is it a bug or am I missing something?



Solution 1:[1]

You are asking 2 questions

  • why does the code get eliminated
  • why is that printf executed

It gets eliminated becuase arithmetic on a pointer can never yield nullptr. So it gets treated as

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

Arithmetic on NULL is UB, once you do that all bets are off. printf might happen, might not

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pm100