How do you explain that line 7 gets a warning, but not line 5 or line 6?
int main()
{
unsigned char a = 0xFF;
unsig
Compilers are built by people and they don't have infinite time to figure out all arithmetic possibilities to decide, which cases are worth issuing a warning.
So I believe (attention opinion) that compiler engineers would go the following way:
I would expect people to write code where either the result is casted to (unsigned char)
or where the outermost operator masks all higher bytes with a constant.
a = (unsigned char) ( /* some obscure bit-wise expressoin */ );
would be OK thena = 0xff & ( /* some obscure bit-wise expressoin */ );
also OKif you know that your compiler translates those two patterns correctly the other cases shouldn't bother you too much.
I've seen compilers that would issue a warning because of a = a | b;
so GCC not giving a warning is a free bonus. it might be, that gcc just infers the constant assignment in a | b
and therefore replaces it with 0xff | 0xff
which is known to work without problems. If that happens though I don't know why it cannot derive the constant value of a
in the other statements.
I use linux x86_64, GCC 4.70. And get the same error. I compile the code, and use gdb to disassemble the execution file. Here is what I get.
(gdb) l
1 int main(){
2 unsigned char a = 0xff;
3 unsigned char b = 0xff;
4 a = a | b;
5 a = (unsigned char)(b & 0xf);
6 a |= (unsigned char)(b & 0xf);
7 return 0;
8 }
(gdb) b 4
Breakpoint 1 at 0x4004a8: file test.c, line 4.
(gdb) b 5
Breakpoint 2 at 0x4004af: file test.c, line 5.
(gdb) b 6
Breakpoint 3 at 0x4004b9: file test.c, line 6.
(gdb) r
Starting program: /home/spyder/stackoverflow/a.out
Breakpoint 1, main () at test.c:4
4 a = a | b;
(gdb) disassemble
Dump of assembler code for function main:
0x000000000040049c <+0>: push %rbp
0x000000000040049d <+1>: mov %rsp,%rbp
0x00000000004004a0 <+4>: movb $0xff,-0x1(%rbp)
0x00000000004004a4 <+8>: movb $0xff,-0x2(%rbp)
=> 0x00000000004004a8 <+12>: movzbl -0x2(%rbp),%eax
0x00000000004004ac <+16>: or %al,-0x1(%rbp)
0x00000000004004af <+19>: movzbl -0x2(%rbp),%eax
0x00000000004004b3 <+23>: and $0xf,%eax
0x00000000004004b6 <+26>: mov %al,-0x1(%rbp)
0x00000000004004b9 <+29>: movzbl -0x2(%rbp),%eax
0x00000000004004bd <+33>: mov %eax,%edx
0x00000000004004bf <+35>: and $0xf,%edx
0x00000000004004c2 <+38>: movzbl -0x1(%rbp),%eax
0x00000000004004c6 <+42>: or %edx,%eax
0x00000000004004c8 <+44>: mov %al,-0x1(%rbp)
0x00000000004004cb <+47>: mov $0x0,%eax
0x00000000004004d0 <+52>: pop %rbp
0x00000000004004d1 <+53>: retq
End of assembler dump.
the a = a | b
is compiled to
movzbl -0x2(%rbp),%eax
or %al,-0x1(%rbp)
the a = (unsigned char)(b & 0xf)
is compiled to
mov %al,-0x2(%rbp)
and $0xf,%eax
mov %al,-0x1(%rbp)
the a |= (unsigned char)(b & 0xf);
is compiled to
movzbl -0x2(%rbp),%eax
mov %eax,%edx
and $0xf,%edx
movzbl -0x1(%rbp),%eax
or %edx,%eax
mov %al,-0x1(%rbp)
the explict cast didn't appear in asm code. The problem is when (b & 0xf) operation is done. the output of operation is sizeof(int)
.
So you should use this instead:
a = (unsigned char)(a | (b & 0xF));
PS: explict cast dont generate any warning. even you will lose something.
The return type of bitwise operator & is integer. Whenever you cast an int (4 bytes) into char or unsigned char (1 byte) it gives you warning.
So this is not related to bitwise operator it is related to typecasting from 4 bytes variable to 1 bytes variable.
I think the problem is that you convert int
to unsigned char
, AND back to int
.
Line 6 converts int
to unsigned char
, but just stores it into unsigned char
.
Line 7 converts int
to unsigned char
, and then, in order to do arithmetic, converts it back to int
. The new integer may be different from the original, so you get a warning.