C中按位操作后的类型转换警告

C中按位操作后的类型转换警告,c,type-conversion,bit-manipulation,C,Type Conversion,Bit Manipulation,您如何解释第7行收到警告,而不是第5行或第6行 intmain() { 无符号字符a=0xFF; 无符号字符b=0xFF; a=a | b;//5:(无警告) a=(无符号字符)(b&0xF);//6:(无警告) a=a |(无符号字符)(b&0xF);//7:(警告) 返回0; } GCC 4.6.2在32位体系结构(Windows PC)上编译时的输出: 如果这有助于您理解我的问题,以下是我的看法(可能不正确!): 我假设在32位机器上,操作是在32位数字上完成的。由于unsigned c

您如何解释第7行收到警告,而不是第5行或第6行

intmain()
{
无符号字符a=0xFF;
无符号字符b=0xFF;
a=a | b;//5:(无警告)
a=(无符号字符)(b&0xF);//6:(无警告)
a=a |(无符号字符)(b&0xF);//7:(警告)
返回0;
}
GCC 4.6.2在32位体系结构(Windows PC)上编译时的输出:

如果这有助于您理解我的问题,以下是我的看法(可能不正确!):

我假设在32位机器上,操作是在32位数字上完成的。由于
unsigned char
适合32位
int
,因此运算结果是32位
int
。但是,由于GCC没有在第5行和第6行上给出警告,我想还有其他的事情发生:

第5行:GCC数据表明(uchar)或(uchar)永远不会大于最大值(uchar),因此没有警告

第6行:GCC表示(uchar)和0xF永远不会大于MAX(uchar),因此没有警告。甚至不需要显式强制转换

第7行:基于上述假设:不应发出警告(从第6行开始),或也不应发出警告(从第5行开始)


我想我的逻辑在哪里有问题。帮助我理解编译器的逻辑。

我使用linux x86_64,GCC 4.70。并得到相同的错误。 我编译代码,并使用gdb反汇编执行文件。这是我得到的

(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.
a=(无符号字符)(b&0xf)
编译为

mov    %al,-0x2(%rbp)
and    $0xf,%eax
mov    %al,-0x1(%rbp)
movzbl -0x2(%rbp),%eax
mov    %eax,%edx
and    $0xf,%edx
movzbl -0x1(%rbp),%eax
or     %edx,%eax
mov    %al,-0x1(%rbp)
a |=(无符号字符)(b&0xf)编译为

mov    %al,-0x2(%rbp)
and    $0xf,%eax
mov    %al,-0x1(%rbp)
movzbl -0x2(%rbp),%eax
mov    %eax,%edx
and    $0xf,%edx
movzbl -0x1(%rbp),%eax
or     %edx,%eax
mov    %al,-0x1(%rbp)
asm代码中未出现解释性强制转换。问题在于(b&0xf)操作何时完成。操作的输出是
sizeof(int)
。 所以你应该用这个来代替:

a = (unsigned char)(a | (b & 0xF));

注:请不要产生任何警告。甚至你也会失去一些东西。

我认为问题在于你把
int
转换成
无符号字符,然后再转换回
int

第6行将
int
转换为
unsigned char
,但只将其存储到
unsigned char


第7行将
int
转换为
unsigned char
,然后,为了进行算术运算,将其转换回
int
。新整数可能与原始整数不同,因此您会收到警告。

按位运算符的返回类型为整数。每当您将int(4字节)转换为char或unsigned char(1字节)时,它都会向您发出警告


因此,这与按位运算符无关,它与从4字节变量到1字节变量的类型转换有关。

编译器是由人构建的,他们没有无限的时间来计算所有算术可能性来决定哪些情况值得发出警告

因此,我相信(注意意见)编译器工程师将采取以下方式:

  • 如果代码看起来可能出错,通常会发出警告
  • 找出所有明显的情况,在这些情况下,编译器可以很容易地进行修改
  • 将其余的警告保留为误报,因为此人要么知道自己在做什么,要么会因为编译器发出警告而感到放心
我希望人们在编写代码时,要么将结果强制转换为
(unsigned char)
,要么将最外层的运算符用常量屏蔽所有较高的字节

  • a=(无符号字符)(/*一些模糊的位表达式*/)就可以了
  • a=0xff&(/*一些模糊的位表达式*/)也可以
如果您知道编译器正确地翻译了这两种模式,那么其他情况就不会太麻烦您了


我见过编译器会发出警告,因为
a=a | b所以GCC不发出警告是免费的奖励。可能是,gcc只是推断出
a | b
中的常量赋值,因此将其替换为
0xff | 0xff
,这是已知的工作没有问题的。如果发生这种情况,尽管我不知道为什么它不能在其他语句中派生出
a
的常量值。

这看起来像是编译器中的一个bug:mac上基于clang的编译器使用您指定的设置生成一个无警告编译。Linux/x86-64上的GCC 4.4.5没有警告。有人能确认他们得到的是相同的吗警告和我一样?是的,我得到了与GCC 4.4.3/linux相同的警告。我认为答案可能在某个地方:
为什么Wconversion在变量之间的隐式转换中发出警告,即使在编译时知道值没有更改?
警告,因为前端没有流控制(我们不知道D的值).<代码>你考虑了第5行吗?使用你的逻辑,第5行不应该得到警告吗?“第5行取<代码>无符号char < /代码>,然后,为了进行算术运算,将其转换为<代码> int <代码>…根据C的规则,赋值运算符右侧的所有3个表达式都是
int
类型。它们在这方面没有区别。@Alex,只有第7行从
int
转换为
unsigned char
并返回到
int
。好吧,我对第二种情况的看法是错误的,但是
sizeof(a | b)
=
sizeof(a |(unsigned char)(b&0xF))
=
sizeof(int)
。在
a | b
中,
无符号字符
被转换成
int
,就是这样。在
a |(无符号字符)(b&0xF)
中,
b&0x0f
,即
int
,被转换成
无符号字符
,然后返回到
int
无符号字符(
mov%al,-0x1(%rbp)
有效地进行了转换。我认为OP不应该以任何不同的方式编写代码。这是有效和合理的C代码。@Alex看看这个
或%al,-0x1(%rbp)
和$oxf,%eax
,这一差异意味着第二个需要一个明确的转换。第三个明确的转换不需要