简短的回答
您正在以两种不同的方式评估同一个表达式——一次在 x86 上运行时,一次在编译时。(我假设你在编译时禁用了优化,见下文。)
长答案
查看反汇编的可执行文件,我注意到以下内容:第一个参数printf()
是在运行时计算的:
movl $0x0,-0x10(%ebp)
mov -0x10(%ebp),%ecx ; ecx = 0 (int n)
mov $0x20,%edx ; edx = 32
sub %ecx,%edx ; edx = 32-0 = 32
mov %edx,%ecx ; ecx = 32
mov $0x1,%edx ; edx = 1
shl %cl,%edx ; edx = 1 << (32 & 31) = 1 << 0 = 1
add $0xffffffff,%edx ; edx = -1 + 1 = 0
移位由 x86SHL
指令执行,%cl
作为其运算符。根据英特尔手册:“目标操作数可以是寄存器或内存位置。计数操作数可以是立即数或寄存器 CL。计数被屏蔽为 5 位,这将计数范围限制为 0 到 31。一个特殊的为计数 1 提供操作码编码。”
对于上面的代码,这意味着您正在移动0
,因此1
在 shift 指令之后保留原位。
相反,第二个的参数printf()
本质上是一个由编译器计算的常量表达式,编译器不会屏蔽移位量。因此,它执行 32b 值的“正确”移位:1<<32 = 0
然后将其添加-1
到该值上——您会看到0+(-1) = -1
结果。
这也解释了为什么您只看到一个warning: left shift count >= width of type
而不是两个,因为警告源于编译器评估 32b 值移位 32 位。编译器没有发出任何关于运行时偏移的警告。
减少测试用例
以下是将您的示例简化为基本要素:
#define N 0
int n = 0;
printf("%d %d\n", 1<<(32-N) /* compiler */, 1<<(32-n) /* runtime */);
打印0 1
显示转变的不同结果。
一个警告
请注意,上面的示例仅适用于-O0
已编译的代码,您没有让编译器在编译时优化(评估和折叠)常量表达式。如果您采用简化的测试用例并对其进行编译,那么您可以从此优化代码-O3
中获得相同且正确的结果:0 0
movl $0x0,0x8(%esp)
movl $0x0,0x4(%esp)
我认为如果您更改测试的编译器选项,您将看到相同的更改行为。
注意gcc-4.2.1(和其他?)中似乎存在一个代码生成错误,其中运行时结果0 8027
由于优化中断而刚刚关闭。