【问题标题】:Why the output of `printf("%llu\n", 1ull << n);` and `printf("%llu\n", 1ull << 64);` is different in C++? (n=64) [duplicate]为什么`printf("%llu\n", 1ull << n);`和`printf("%llu\n", 1ull << 64);`在C++中的输出不同? (n = 64)[重复]
【发布时间】:2019-12-03 10:07:05
【问题描述】:

为什么 printf("%llu\n", 1ull &lt;&lt; n);printf("%llu\n", 1ull &lt;&lt; 64); 在 C++ 中的输出不同?

代码:

#include <cstdio>

int main()
{
    int n = 64;
    printf("%llu\n", 1ull << n);
    printf("%llu\n", 1ull << 64);
    return 0;
}

输出:

1
0

【问题讨论】:

  • 链接的问题是关于右移,但标准不区分这两者。
  • @Yksisarvinen 感谢您的澄清。我以为我找到了左移答案。我将保持原样,因为-正如您所说-标准不区分这两者。

标签: c++ c undefined-behavior bit-shift


【解决方案1】:

here:

在任何情况下,如果右操作数的值为负数或 大于或等于提升的左操作数中的位数, 行为未定义。

如果您的左操作数是 64 位,而您的右操作数是 64,这是未定义的行为,然后任何事情都可能发生而没有任何一致性保证。

您的编译器也应该为此发出警告,至少当我使用 GCC 或 Visual Studio 尝试它时它会发出警告。

【讨论】:

【解决方案2】:

原因是像1&lt;&lt;64 这样的表达式是编译时常量,实际上是由编译器在编译时计算的。不会发出任何转换任何内容的代码。

表达式1&lt;&lt;64 被编译器评估为0,这是合理且合法的,因为正如其他人指出的那样,行为实际上是未定义的。为uint64_t i = (uint64_t)1 &lt;&lt; 64; 生成的程序集只是将零存储在变量的位置:

QWORD PTR [rbp-16], 0

现在,对于 非编译时间值,代码会被发出。 uint64_t i2 = (uint64_t)1 &lt;&lt; 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。

【讨论】:

    【解决方案3】:

    来自 C 标准(6.5.7 位移位运算符)

    3 对每个操作数执行整数提升。这 结果的类型是提升的左操作数的类型。 如果 右操作数的值为负数或大于等于 提升的左操作数的宽度,行为未定义

    所以程序有未定义的行为。

    输出的差异可以解释为编译器使用整数常量(字面量)生成的目标代码与使用变量时的代码不同。

    【讨论】:

      猜你喜欢
      • 2018-08-12
      • 2015-02-15
      • 2014-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多