Search code examples
cbooleanincrementundefined-behaviordecrement

Is decrement of bool variable defined in С?


This question is about С. Say we have a code like this:

bool a = false;
a++;
printf("%d\n", a);
a--;
printf("%d\n", a);

This on my x86-64 linux machine shows:

1
0

That was not a surprise for me. And this code:

bool a = false;
a++; a++;
printf("%d\n", a);
a--; a--;
printf("%d\n", a);

was kind of a surprise since it prints:

1
1

This is consistent on some other architectures (I checked x86 and arm7).

C standard says that e++ or e-- should be viewed as e+=1 or e-=1 respectively. And indeed if we replace a++; with a += 1; and a--; with a-= 1; output stays the same.

I looked at the assembly for x86-64. gcc uses 'xor' instruction to do decrement:

    b--; b--;
    11e6:       80 75 ff 01             xor    BYTE PTR [rbp-0x1],0x1
    11ea:       80 75 ff 01             xor    BYTE PTR [rbp-0x1],0x1
    printf("%d\n", b);
    11ee:       0f b6 45 ff             movzx  eax,BYTE PTR [rbp-0x1]
    11f2:       89 c6                   mov    esi,eax
    11f4:       48 8d 05 19 0e 00 00    lea    rax,[rip+0xe19]        # 2014 <_IO_stdin_used+0x14>
    11fb:       48 89 c7                mov    rdi,rax
    11fe:       b8 00 00 00 00          mov    eax,0x0
    1203:       e8 68 fe ff ff          call   1070 <printf@plt>

And clang prefers to use 'add' (!) and 'and' for decrement:

    11c9:       04 01                   add    al,0x1
    11cb:       24 01                   and    al,0x1
    11cd:       88 45 fb                mov    BYTE PTR [rbp-0x5],al
    11d0:       8a 45 fb                mov    al,BYTE PTR [rbp-0x5]
    11d3:       24 01                   and    al,0x1
    11d5:       0f b6 f0                movzx  esi,al
    11d8:       48 8d 3d 36 0e 00 00    lea    rdi,[rip+0xe36]        # 2015 <_IO_stdin_used+0x15>
    11df:       b0 00                   mov    al,0x0
    11e1:       e8 4a fe ff ff          call   1030 <printf@plt>

But the result is the same. If I understand correctly these are just different methods to flip least significant bit.

None of the textbooks that I know of show example like this, so I presume this isn't widely known fact. And probably it is my own ignorance, but I have programmed in C for some time and only now learned of this weird [or not?] behaviour.

Full source is here.

My questions:

  1. Is decrement of bool variable defined according to C standard? Or is it undefined (or maybe implementation-defined) behaviour?

  2. If increment and decrement of bools are defined, why gcc shows warnings about a++ and a-- when given -Wall flag?

  3. Сonsecutive decrements of bool variable flip its value from 0 to 1 and again to 0 and so forth. This is in contrast to what increment does (it does not flip). Is it deliberately chosen and portable behaviour?


Solution

  • It is defined (and thus portable[1]).

    C17 §6.5.2.4 ¶2 [...] As a side effect, the value of the operand object is incremented (that is, the value 1 of the appropriate type is added to it). [...]

    C17 §6.5.2.4 ¶23 The postfix-- operator is analogous to the postfix++ operator, except that the value of the operand is decremented (that is, the value 1 of the appropriate type is subtracted from it).

    C17 §6.5.3.1 ¶2 [...] The expression++E is equivalent to (E+=1). [...]

    C17 §6.5.3.1 ¶3 The prefix-- operator is analogous to the prefix++ operator, except that the value of the operand is decremented.

    C17 §6.5.16.2 ¶3 A compound assignment of the form E1 op= E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once [...]

    (I could go on to show that addition performs integer promotions, that true as an int is 1, that false as an int is 0, etc. But you get the idea.)

    And that is the behaviour we observe.

    bool a = false;
    a++;                    # false⇒0, 0+1=1,  1⇒true
    printf("%d\n", a);      #                            true⇒1
    a++;                    # true⇒1,  1+1=2,  2⇒true
    printf("%d\n", a);      #                            true⇒1
    a--;                    # true⇒1,  1-1=0,  0⇒false
    printf("%d\n", a);      #                            false⇒0
    a--;                    # false⇒0, 0-1=-1, -1⇒true
    printf("%d\n", a);      #                            true⇒1
    

    "⇒" indicate integer promotion or implicit conversion to bool.

    It warns because it's weird. Addition and subtraction aren't boolean operations. And there are far clearer alternatives (at least for prefix-increment and postfix-increment, or if you discard the value returned). From the above, we derive the following equivalencies for some bool object b:

    • ++b is equivalent to b = true.
    • --b is equivalent to b = !b.

    1. It's portable as long as you have a C compiler. @Weather Vane has indicated that --b produces false unconditionally in MSVC, but it's well known that MSVC is not actually a C compiler.