为什么在C ++中printf("%llu\n",1ull << n);
和printf("%llu\n",1ull << 64);
的输出不同?
代码:
#include <cstdio>
int main()
{
int n = 64;
printf("%llu\n",1ull << n);
printf("%llu\n",1ull << 64);
return 0;
}
输出:
1
0
为什么在C ++中printf("%llu\n",1ull << n);
和printf("%llu\n",1ull << 64);
的输出不同?
代码:
#include <cstdio>
int main()
{
int n = 64;
printf("%llu\n",1ull << n);
printf("%llu\n",1ull << 64);
return 0;
}
输出:
1
0
请参见here:
无论如何,如果右操作数的值为负或为 大于或等于提升后的左操作数中的位数, 行为是不确定的。
如果您的左操作数是64位,而右操作数是64
,则这是未定义的行为,然后任何事情都可能发生,而没有任何一致性的保证。
您的编译器也应该为此发出警告,至少当我在GCC或Visual Studio中尝试时,它会对我造成影响。
,原因是像1<<64
这样的表达式是编译时常量,实际上是由编译器在编译时计算的。没有发出转移任何内容的代码。
表达式1<<64
被编译器评估为0
,这是合理且合法的,因为正如其他人指出的那样,该行为实际上是未定义的。为uint64_t i = (uint64_t)1 << 64;
生成的程序集只是将零存储在变量的位置:
QWORD PTR [rbp-16],0
现在,发出非编译时间值代码。 uint64_t i2 = (uint64_t)1 << n;
转换为
mov rax,QWORD PTR [rbp-8]
mov edx,1
mov ecx,eax
sal rdx,cl
mov rax,rdx
mov QWORD PTR [rbp-24],rax
实际SAL移位指令之前和之后的所有样板代码只是将操作数移动到位,并将结果移动到变量中。重要的是,编译器确实发出了将1移到此处的代码。因为对于64位值,移位超过63是非法且毫无意义的,因此英特尔处理器会静静地mask the shift value:
REX.W形式的REX前缀[我必须假设在这里发生]将操作提升为64位,并将CL的掩码宽度设置为6位。
也就是说,处理器在内部用63 / 11'1111掩盖了n的64 / 100'0000值,从而将移位值设为0。结果当然是原始值1。
在更高的优化级别下,编译器也会优化该指令,因为它可以推断出非易失性n
的值,并且在那里也发出0。
根据C标准(6.5.7位移位运算符)
3对每个操作数执行整数提升。的 结果的类型是提升后的左操作数的类型。 如果 右操作数的值是负数或大于或等于 提升的左操作数的宽度,行为未定义
因此,该程序具有未定义的行为。
可以通过以下方式解释输出的差异:使用整数常量(文字)时,与使用变量时的代码相比,编译器生成不同的目标代码。