【问题标题】:Integer casting bug?整数转换错误?
【发布时间】:2026-02-01 22:50:01
【问题描述】:

考虑以下代码:

#include <iostream>
using namespace std;

int main() {
    // the following is expected to not print 4000000000
    // because the result of an expression with two `int`
    // returns another `int` and the actual result 
    // doesn't fit into an `int` 
    cout << 2 * 2000000000 << endl; // prints -294967296

    // as such the following produces the correct result
    cout << 2 * 2000000000U << endl; // prints 4000000000
}

我尝试将结果转换为不同的整数类型,但遇到了一些奇怪的行为。

#include <iostream>
using namespace std;

int main() {
    // unexpectedly this does print the correct result
    cout << (unsigned int)(2 * 2000000000) << endl; // prints 4000000000

    // this produces the same wrong result as the original statement
    cout << (long long)(2 * 2000000000) << endl; // prints -294967296
}

我预计以下两个语句都不会产生正确的结果,为什么一个成功而另一个没有?

【问题讨论】:

  • 尝试将 LL 后缀添加到数字,数字 2 * 2000000000 是一个表达式,它被评估为 int(溢出)然后转换为 long long
  • 将 int 转换为 unsigned int 将起作用,因为这些位是相同的,只是程序理解这些位的方式在转换前后有所不同。
  • 溢出后,任何事情都可能发生,包括通常你想发生的事情,只是为了迷惑你;-)
  • @JonTaylor:不。转换是在 values 而不是位上完成的。从有符号到无符号的转换是以无符号整数中位的最大值为模完成的。事实上,在二的互补机器上,这意味着简单地以不同的方式解释位,这一事实是简洁但无关紧要的。重要的是(经过一些数学运算)2^32 - 294967296 = 4000000000。无论位表示如何,它都可以工作。
  • @GManNickG:不过,关键是2 * 2000000000 会导致-294967296,这在很大程度上取决于系统和位表示(特别是因为在这种情况下它无论如何都是未定义的行为) .但是,是的,使魔术起作用的不是演员(就像您正确指出的那样);这是 2 对 int 的补充,以及处理溢出的方式使其工作。只是想让它更清楚一点。

标签: c++ casting integer integer-overflow


【解决方案1】:

在试图回答这个问题的人中发生了太多的困惑。

让我们检查一下:

2 * 2000000000

这是int 乘以int。 §5/4 告诉我们:

如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。

这个结果是数学定义的,但它是否在int 的可表示值范围内?

这取决于。在许多常见架构上,int 有 32 位来表示值,最大值为 2,147,483,647。由于其数学结果是 4,000,000,000,因此这样的架构将无法表示该值并且行为未定义。 (这几乎解决了这个问题,因为现在整个程序的行为是未定义的。)

但这仅取决于平台。如果 int 改为 64 位宽(注意:long long 保证至少有 64 位来表示值),结果会很合适。

让我们稍微解决一下问题,然后直奔主题:

int x = -294967296; // -294,967,296

让我们进一步说这符合 int 的范围(对于 32 位 int 它确实如此)。

现在让我们将其转换为unsigned int

unsigned int y = static_cast<unsigned int>(x);

y 的值是多少? x的位表示无关

没有“位转换”,编译器只是将位视为无符号数量。转换适用于signed int 转换为 unsigned int 在 §4.7/2 中定义:

如果目标类型是无符号的,则结果值是与源整数一致的最小无符号整数(模 2n,其中 n 是用于表示无符号类型的位数)。 [注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。 ——尾注]

对于我们在 32 位 (unsigned) int 系统上的我们来说,这意味着 4000000000。这与位无关:two's-compliment、one's-compliment、magic's-compliment 等。这些是无关紧要的。

原因你在第一个地方看到你想要的值(忽略 UB)是在你的二进制恭维机器上,有符号和无符号整数之间的区别确实是不同查看位的问题.因此,当您将这两个 int 相乘时,您“实际上”将两个无符号整数相乘,忽略了溢出,并将结果视为有符号整数。然后演员再次改变你的看法。

但是铸造独立于比特!

【讨论】:

    【解决方案2】:

    在一个int中,4,000,000,000的值写成1110 1110 0110 1011 0010 1000 0000 0000

    在无符号整数中,4,000,000,000 的值写为1110 1110 0110 1011 0010 1000 0000 0000

    看这两个,你可以看到它们是一样的。

    区别在于intunsigned int 中读取位的方式。在常规的int 中,most significant bit 用于判断数字是否为负。

    【讨论】:

      【解决方案3】:

      在 C++ 中,表达式的类型不依赖于代码环境(通常)。

      因此子表达式 2 * 2000000000 在同一系统上具有相同的类型和值,无论包含表达式的上下文是什么,它都是int(因为*运算符的两个操作数都是ints)。它会是 4000000000,但是在您的架构上,由于溢出,它更改为 -294967296。

      将其转换为 long long 不会改变值,因为 long long 可以代表 -294967296 就好了。

      实际上,cout &lt;&lt; (unsigned int)(2 * 2000000000) &lt;&lt; endl; 工作更有趣。由于unsinged int 不能容纳-294967296,再次发生溢出。 -294967296 和 4000000000 模 2^32 是全等的,所以这将是新值。 (更新自 GManNickG 的更好答案)。

      为了说明更深层次的问题,你可以尝试

      cout << (unsigned int)(2 * 2000000000 / 2) << endl;
      

      除法将在 -294967296 上执行,并且 -147483648 的二进制表示将转换为无符号数,即 4147483648

      【讨论】:

      • 在 C++ 中,表达式的类型不依赖于环境。 - 废话。标准中没有任何内容表明int 的长度为 32 位,并且使用二进制组件表示。表达式 2*2000000000 是否溢出在很大程度上取决于机器。
      • @DavidHammen 不过我觉得Csq的意思是不管什么环境,2*2000000000的类型都是int。该值取决于实现细节,但“表达式的类型”不。
      • 那是我的意思,更新了答案以更准确地说我的意思是代码环境。
      • ... 或者子表达式 2*2000000000 在同一系统上具有相同的类型和值,无论包含表达式的上下文是什么。 (虽然当你得到重载函数和初始化列表的地址时,这个论点会分崩离析。)
      【解决方案4】:

      在第三种(奇怪的)情况下,正在运行的程序会这样做:

      2 * 2000000000       = binary number (11101110011010110010100000000000)
      print it as unsigned = 4000000000 
                         (interprets the first bit (1) as part of the unsigned number)
      

      第四种情况:

      2 * 2000000000       = binary number (11101110011010110010100000000000, same as above) 
      print it as signed   = -294967296 
                         (interprets the first bit (1) as negative number)
      

      要学习的重要一点是表达式 2 * 2000000000 会产生一个字节序列,然后将其解释为强制转换操作所说的那样。

      【讨论】:

        【解决方案5】:

        请注意,有符号整数溢出是未定义的行为。总而言之,任何事情都可能发生。包括完全正确的结果。


        整数文字 22000000000 都是 32 位宽。结果会溢出,正如你的编译器告诉你的那样:

        warning: integer overflow in expression [-Woverflow]
        

        乘法的结果仍然是一个 32 位有符号整数。而且,在这种情况下,幸运的是,溢出的结果是正确的结果,当被视为无符号 32 位整数时。您可以在将位模式转换为 32 位 unsigned int 时观察到这一点。

        但是,如果将值转换为更大宽度的整数类型(例如 64 位),则前导字节将用ff (sign extension) 填充,从而给出错误的结果。

        #include <iostream>
        
        int main() {
            long long x = 2 * 2000000000;     // 8 byte width
            unsigned int y = 2 * 2000000000;  // 4 byte width
            unsigned long z = 2 * 2000000000; // 8 byte width
            std::cout << std::hex << x << " " << std::dec << x << std::endl;
            // output is: ffffffffee6b2800 -294967296
            std::cout << std::hex << y << " " << std::dec << y << std::endl;
            // output is: ee6b2800 4000000000
            std::cout << std::hex << z << " " << std::dec << z << std::endl;
            // output is: ffffffffee6b2800 18446744073414584320
        
        }
        

        【讨论】: