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:
Is decrement of bool variable defined according to C standard? Or is it undefined (or maybe implementation-defined) behaviour?
If increment and decrement of bools are defined, why gcc shows warnings about a++ and a-- when given -Wall flag?
С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?
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
.--b
produces false
unconditionally in MSVC, but it's well known that MSVC is not actually a C compiler.