为什么printf("%llun", 1ull << n);
和printf("%llun", 1ull << 64);
的输出在C++上是不同的?
法典:
#include <cstdio>
int main()
{
int n = 64;
printf("%llun", 1ull << n);
printf("%llun", 1ull << 64);
return 0;
}
输出:
1
0
看这里:
在任何情况下,如果右操作数的值为负数或 大于或等于提升的左操作数中的位数, 行为未定义。
如果你的左操作数是 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 是非法且毫无意义的,英特尔处理器会默默地屏蔽移位值:
REX 前缀采用 REX 的形式。W [我必须假设这里发生这种情况] 将操作提升到 64 位,并将CL 的掩码宽度设置为 6 位。
也就是说,处理器在内部用 63/11'1111 屏蔽 n 的值 64/100'0000,导致移位值为 0。结果当然是原来的 1。
随着优化级别越高,编译器也会优化该指令,因为它可以推断出非易失性n
的值,并且在那里也发出0。
来自 C 标准(6.5.7 按位移位运算符(
3 整数提升对每个操作数执行。这 结果的类型是提升的左操作数的类型。如果 右操作数的值为负数或大于或等于 提升的左操作数的宽度,行为未定义
因此,程序具有未定义的行为。
输出的差异可以通过以下方式解释:编译器在使用整数常量(文字(时生成不同的目标代码,与使用变量时的代码相比。