为什么在C ++中,`printf(“%llu \ n”,1ull << n);和`printf(“%llu \ n”,1ull << 64);的输出不同? (n = 64)

为什么在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
canyu1 回答:为什么在C ++中,`printf(“%llu \ n”,1ull << n);和`printf(“%llu \ n”,1ull << 64);的输出不同? (n = 64)

请参见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对每个操作数执行整数提升。的   结果的类型是提升后的左操作数的类型。 如果   右操作数的值是负数或大于或等于   提升的左操作数的宽度,行为未定义

因此,该程序具有未定义的行为。

可以通过以下方式解释输出的差异:使用整数常量(文字)时,与使用变量时的代码相比,编译器生成不同的目标代码。

本文链接:https://www.f2er.com/2986947.html

大家都在问