【问题标题】:Why does printf and cout gives different output for this code?为什么 printf 和 cout 会为此代码提供不同的输出?
【发布时间】:2013-10-25 14:43:20
【问题描述】:
int main(){
    ll a=pow(2,32);
    cout <<a<<endl;
    cout << (-1<<1)<<endl;
    printf("%x",-1<<1);
}

对于上面的代码,我得到以下输出:

4294967296
-2
fffffffe

十进制的4294967296 等于十六进制的fffffffe,基本上是2^32。为什么 printf 和 cout 的行为不同?这种转变究竟是如何运作的?

【问题讨论】:

  • 改用printf("%d", ...)
  • 4294967296 肯定不等于 fffffffe。前者是2^32,后者是2^32-2
  • 对于左移负数是否未定义存在不同的解释。 John 创建了一个question 看看我们是否可以解决它。

标签: c++ bit-shift


【解决方案1】:

printf 格式标志%x 表示以十六进制打印整数值。

还有一个流操纵器可以完成此操作 (std::hex),但您没有使用它。当您将整数值输出到没有操纵器的流时,它以 10 为底输出。

See here 了解有关 printf 格式标志的更多信息,and here 了解有关流操纵器的信息。

移位运算符&lt;&lt; 的工作方式如 C++03 标准 (14882:2003) 中所述:

5.8 移位运算符

1/ 移位运算符 > 从左到右分组。 移位表达式: 加法表达式 移位表达式 >加法表达式

操作数应为整数或枚举类型且为整数 进行促销。结果的类型是 提升左操作数。如果正确的操作数,则行为未定义 为负数,或大于或等于 提升左操作数。

2/ E1

[注意:常量 ULONG_MAX 和 UINT_MAX 在标题中定义 )。 ]

在您的情况下,二进制中的值-1 的每一位都是1s。对于 32 位值,则:

11111111 11111111 11111111 11111111 

如果你使用&lt;&lt; 将这个左移一位,你会得到:

11111111 11111111 11111111 11111110 

以 10 为底的 -2。由于运算-1&lt;&lt;1 对 LHS 使用负数,因此整个表达式是有符号(非无符号)类型。

【讨论】:

  • 但是为什么在 cout 的情况下打印“-2”?它不应该打印“4294967296”吗?我在问为什么这两个值不同。我了解十六进制和十进制表示之间的区别。
  • @JohnDibling 您只需要 32 位即可显示 -1 &lt;&lt; 1,因为它们都是 ints 而不是 long longs。
  • @ZacHowland:我知道,但是 OP 的代码使用 ll 作为 a 的类型声明,我猜这意味着 uint64_t
  • @JohnDibling 对于a,是的。之后他所做的一切都是处理 32 位 ints(这是他的整体问题的很大一部分)。
  • @ZacHowland:我想你是对的。然后我制作了 32 位值。
【解决方案2】:

首先,

ffffffffe 十六进制,基本上是 2^32

错了。 FFFFFFFE = 4294967294,即 2^32 - 2(对于无符号整数)或 -2(对于 2 的补码中的 32 位有符号整数)。

其次,printf("%x", ...) 将打印一个无符号的十六进制整数(即unsigned int),在大多数现代系统上它是 32 位的。 long long a = 2 &lt;&lt; 32 需要一个 64 位整数才能正确存储它(或者更准确地说,至少是一个 33 位整数),所以当您使用 cout &lt;&lt; a 时,您调用的是具有正确类型的 ostream&amp; operator&lt;&lt;(ostream&amp;, long long)。也就是说,由于 printf 说明符使用的类型与 C++ operator&lt;&lt; 重载使用的强类型相比,您遇到了溢出问题。

【讨论】:

    【解决方案3】:

    这段代码调用undefined behavior,因为你试图左移一个负数,the draft C++11 Standard部分5.8移位运算符说(强调我的):

    E1的值E1 有一个有符号类型 和非负值,并且E1×2E2可以在结果中表示 类型,那么这就是结果值;否则,行为是 未定义

    这对于draft C99 standard 部分6.5.7 也是相同的位移运算符

    invalid 转换说明符指定为 printf 也是 未定义的行为,您指定的是 %x,它期望 unsigned int 但结果是签名7.19.6.1 部分中的C99 草案 fprintf 函数 段落9 中说:

    如果转换规范无效,则行为未定义。248) 如果任何参数不是相应转换规范的正确类型,则行为是 未定义。

    【讨论】:

    • E1 这里没有非负值。另外,这不符合我的标准(C++03)
    • @JohnDibling 这是n3485,据我所知,语言已从03 更改。正确,所以引用说E1 是非负数,然后它被定义为否则它是未定义的,适合这种情况。
    • 编辑了您的帖子以包含已发布标准中的文本,而不是草稿。有一些区别。
    • 那么,您是否将其解释为将负数左移会导致未定义行为?
    • 您是否有意(有效)回滚我的编辑并返回标准草案?
    【解决方案4】:

    %x 格式化输出以显示十六进制值,因此您应该将 std::hex 传递给 cout 以执行相同的操作:

    std::cout << std:::hex << (-1<<1) << endl;
                 ^^^^^^^^^
    

    【讨论】:

      【解决方案5】:

      这将产生与您需要告​​诉它以十六进制打印相同的正确答案

      cout << hex<<(-1<<1)<<endl;
      printf("%x",-1<<1);
      

      【讨论】:

        【解决方案6】:

        %x 是十六进制,因为 cout 只是直接输出数字(int) -1 是 0xFFFFFFFF 向左移动你必须添加一个 0 即最后一个 F(二进制是 1111 变为 1110 即 E 我认为其他人更清楚地回答了您的观点....

        【讨论】:

          【解决方案7】:

          为了便于阅读,我们使用有符号的 8 位整数:

          -1 bitwise is 11111111
          

          现在左移 1:

          -1 << 1
          

          你得到:

          11111110 which is -2
          

          但是,使用转换说明符 %x 告诉 printf()11111111 作为 un 签名,因此它会打印出 fe

          【讨论】:

            【解决方案8】:

            第一种情况是有效的:pow(2,32) 返回精确值 232 作为 double,当您将其转换为 long long 时它仍然是精确的(我假设这就是神秘ll 类型是)。用cout 打印这个是完全有效的。

            (-1&lt;&lt;1) 的情况下,左移一个负数会产生未定义的行为。即使编译器确实将其定义为 -2(大多数情况下),传递一个负数以与 printf%x 说明符(需要无符号类型)一起使用也是未定义的行为。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-06-21
              • 1970-01-01
              • 2021-04-07
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多