在C中按位运算后键入转换警告

Ale*_*lex 11 c bit-manipulation type-conversion

你怎么解释第7行得到警告,但不是第5行或第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;
}
Run Code Online (Sandbox Code Playgroud)

在32位架构(Windows PC)上编译时的GCC 4.6.2输出:

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]
Run Code Online (Sandbox Code Playgroud)

如果这有助于您理解我的问题,这就是我看到的方式(可能不正确!):

我想在32位机器上操作是在32位数字上完成的.由于unsigned char适合32位int,因此运算结果为32位int.但由于海湾合作委员会没有在第5和第6行发出警告,我猜还有其他事情发生:

第5行: GCC认为(uchar)OR(uchar)永远不会大于MAX(uchar),所以没有警告.

第6行: GCC认为(uchar)和0xF永远不会大于MAX(uchar),所以没有警告.甚至不需要显式演员.

第7行:基于上述假设:AND不应发出警告(自第6行起),或者也不应发出警告(自第5行起).

我猜我的逻辑在某处有错.帮助我理解编译器的逻辑.

spy*_*der 0

我使用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.
Run Code Online (Sandbox Code Playgroud)

a = a | b编译为

movzbl -0x2(%rbp),%eax
or     %al,-0x1(%rbp)
Run Code Online (Sandbox Code Playgroud)

a = (unsigned char)(b & 0xf)编译为

mov    %al,-0x2(%rbp)
and    $0xf,%eax
mov    %al,-0x1(%rbp)
Run Code Online (Sandbox Code Playgroud)

a |= (unsigned char)(b & 0xf);编译为

movzbl -0x2(%rbp),%eax
mov    %eax,%edx
and    $0xf,%edx
movzbl -0x1(%rbp),%eax
or     %edx,%eax
mov    %al,-0x1(%rbp)
Run Code Online (Sandbox Code Playgroud)

显式转换没有出现在 asm 代码中。问题是 (b & 0xf) 操作何时完成。操作的输出是sizeof(int). 所以你应该改用这个:

a = (unsigned char)(a | (b & 0xF));
Run Code Online (Sandbox Code Playgroud)

PS:显式强制转换不会生成任何警告。甚至你也会失去一些东西。

  • 在“|”(和“|=”)之前,“a”和“b”被提升为“int”,并且“a|b”的值是“int”类型。按照C标准应该是这样的。编译器有足够的自由来决定如何生成执行标准规定的代码。将这个“|”编译成仅具有8位操作数的指令可能是足够聪明的。它可能足够聪明,可以将其编译为具有 32 位操作数或 32 位和 8 位操作数混合的更快指令。只要程序的行为仍然符合标准,编译器就可以做任何事情。这场灾难证明了一切。 (2认同)