2

I had to spend some time on debugging a strange behaviour with a code that used the ternary operator.

Given the following sample program:

#include <string.h>
#include <stdio.h>

void foo(char c)
{
    printf("%d\n", c);
}

void foo(bool b)
{
    foo((char)(b ? 1 : 0));
}

int main()
{
    for (int i = 0; i < 10; ++i)
    {
        bool b;
        memset(&b, i, 1);
        foo(b);
    }
    return 0;
}

If this code is compiled without optimization, it produce the following output:

$ g++ -O0 -Wall -Wextra -std=c++11 -o test_bool test_bool.cpp
$ ./test_bool 
0
1
1
1
1
1
1
1
1
1

If, instead, it is compiled with optimization (-O1 is already sufficient), it produce the following output:

$ g++ -O1 -Wall -Wextra -std=c++11 -o test_bool test_bool.cpp
$ ./test_bool 
0
1
2
3
4
5
6
7
8
9

If the ternary operator is replaced with an if..else, the optimized code produce the same output of the unoptimized code.

void foo(bool b)
{
    if (b)
        foo((char)1);
    else
        foo((char)0);
}

Analyzing the assembler code produced by the compiler (I have tested gcc from 4.8 to 7.0) I can see that the compile optimize the code (char)(b ? 1 : 0) passing directly the value of b to the other function.

Without the optimization the int foo(bool b) function produce the following assembler code: foo(bool):

foo(bool):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     eax, edi
        mov     BYTE PTR [rbp-4], al
        cmp     BYTE PTR [rbp-4], 0
        je      .L3
        mov     eax, 1
        jmp     .L4
.L3:
        mov     eax, 0
.L4:
        mov     edi, eax
        call    foo(char)
        nop
        leave
        ret

But with the -O1 optimization, the following assembler code is produced:

foo(bool):
        sub     rsp, 8
        movzx   edi, dil
        call    foo(char)
        add     rsp, 8
        ret

(See https://godbolt.org/g/NNAHzJ)

This could be a compiler bug? Optimized and unoptimized versions of the code produce different results.

Urban
  • 399
  • 1
  • 5
  • 11
  • 4
    `u.i = i; foo(u.b);` Using the union this way causes undefined behavior. – πάντα ῥεῖ Dec 13 '16 at 18:38
  • 2
    You're relying to undefined behaviour: http://stackoverflow.com/questions/11373203/accessing-inactive-union-member-and-undefined-behavior – NPE Dec 13 '16 at 18:39
  • 1
    You have undefined behavior. You are only allowed to access one member of a union at a time. If you access the non active member then it is undefined behavior. – NathanOliver Dec 13 '16 at 18:39
  • The question is not about the UB, but about the optimization. The union was the quickest way I could find to simulate what I saw in the code with gdb. – Urban Dec 13 '16 at 19:54
  • @πάνταῥεῖ I changed it to use `memset` instead of `union`, but the result it's the same: the `union` was used just to simplify but it's not part of the original code that caused the problem. – Urban Dec 14 '16 at 08:44
  • 1
    The new code is also UB because a boolean is either true or false, 2 is not valid: http://stackoverflow.com/a/23268553/53974. And UB makes any "miscompilation" legal: here it's only "miscompiled" under optimization, but that's not guaranteed. Finally, I write "miscompilation" in quotes because there's no compiler bug here. – Blaisorblade Dec 15 '16 at 09:51
  • 1
    @Blaisorblade we had that issue with a struct, without using `memset`, but we're not able to reproduce it with a simple snippet :( – deepskyblue86 Dec 15 '16 at 10:02

0 Answers0