问题
How do you explain that line 7 gets a warning, but not line 5 or line 6?
int main()
{
unsigned char a = 0xFF;
unsigned char b = 0xFF;
a = a | b; // 5: (no warning)
a = (unsigned char)(b & 0xF); // 6: (no warning)
a = a | (unsigned char)(b & 0xF); // 7: (warning)
return 0;
}
GCC 4.6.2 output when compiled on 32-bit architecture (Windows PC):
gcc -c main.c --std=c89 -Wall -Wextra -Wconversion -pedantic
main.c: In function 'main':
main.c:7:11: warning: conversion to 'unsigned char' from 'int' may alter its value [-Wconversion]
If this helps you understand my question, here is how I see this (probably incorrect!):
I suppose that on a 32-bit machine operations are done on 32-bit numbers. Since unsigned char fits into 32-bit int, the operation result is 32-bit int. But since GCC doesn't give warnings on lines 5 and 6, I guess there is something else going on:
line 5: GCC figures that (uchar) OR (uchar) is never bigger than MAX(uchar), so no warning.
line 6: GCC figures that (uchar) AND 0xF is never bigger than MAX(uchar), so no warning. Explicit cast is not even necessary.
line 7: Based on assumptions above: AND should not give warning (since line 6), OR should not give warning either (since line 5).
I guess my logic is faulty somewhere there. Help me understand the logic of the compiler.
回答1:
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:
- generally issue a warning if code looks as if it could be wrong.
- find all obvious cases where the compiler can be corrected to work easily.
- leave the rest of the warnings as false positives, because the person either knows what he's doing or will be relieved that the compiler is warning.
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 OK
if 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.
回答2:
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.
回答3:
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.
回答4:
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.
来源:https://stackoverflow.com/questions/11134844/type-conversion-warning-after-bitwise-operations-in-c