【问题标题】:What happens exactly when a 32bit integer overflows on a 64bit machine?当 32 位整数在 64 位机器上溢出时会发生什么?
【发布时间】:2014-05-08 00:41:24
【问题描述】:

情况如下:

  1. 32 位整数溢出
  2. malloc,期望一个 64 位整数使用这个整数作为输入

现在在 64 位机器上,哪个语句是正确的(如果有的话)

假设有符号二进制整数 11111111001101100000101011001000 由于溢出而只是负数。这是一个实际存在的问题,因为您可能希望分配比 32 位整数所能描述的更多的字节。但随后它会以 64 位整数的形式读入。

  1. Malloc 将其读取为 64 位整数,发现 11111111001101100000101011001000################################ 与 # 是通配符位,表示存储在原始整数之后的任何数据。换句话说,它读取的结果接近其最大值 2^64 并尝试分配一些 quintillion 字节。它失败了。
  2. Malloc 将其读取为 64 位整数,转换为 0000000000000000000000000000000011111111001101100000101011001000,可能是因为它是如何加载到寄存器中而留下很多位为零的。它不会失败,而是分配负内存,就像读取正的无符号值一样。
  3. Malloc 将其读取为 64 位整数,转换为 ################################11111111001101100000101011001000,可能是因为它是如何加载到带有 # 通配符的寄存器中的,该通配符表示寄存器中先前存在的任何数据。根据最后一个值,它会非常不可预测地失败。
  4. 整数根本不会溢出,因为即使它是 32 位,它仍然在 64 位寄存器中,因此 malloc 可以正常工作。

我实际上对此进行了测试,导致 malloc 失败(这意味着 1 或 3 是正确的)。我认为 1 是最合乎逻辑的答案。我也知道解决方法(使用 size_t 作为输入而不是 int)。

我真的很想知道实际发生了什么。出于某种原因,对于这种意外的“强制转换”,我没有找到任何关于如何在 64 位机器上实际处理 32 位整数的说明。我什至不确定它是否在寄存器中真的很重要。

【问题讨论】:

  • 这取决于架构。有些架构本身实际上并没有 32 位数字,它们将所有操作视为 64 位。不管有符号溢出是未定义的行为,无符号溢出都有很好的文档记录,并涵盖在二进制补码的工作原理中。
  • @nikolaMM94 但是,溢出有符号整数是未定义的行为 - 根据情况可能会发生很多不同的事情。
  • 这种情况的规则在标准中被称为“通常的算术转换”。它如何适用于您的案例取决于您如何称呼malloc。 (malloc(a+b)c = a + b; malloc(c) 不同,如果 sizeof(c) < sizeof(int)。)遵循通常的算术转换规则,看看在这种情况下会发生什么。
  • malloc 期待 64 位整数?不,它需要一个整数 size_t。您真的应该在 C(或 C++?)中查找有关整数类型的基础知识,特别是如果类型是有符号或无符号的,溢出是完全不同的。
  • 如果您发布代码,而不仅仅是代码描述,将会得到更好的答案。

标签: c++ c 32bit-64bit


【解决方案1】:

您的推理问题在于,它首先假设整数溢出将导致确定性和可预测的操作。

不幸的是,情况并非如此:未定义的行为意味着任何事情都可能发生,尤其是编译器可能会像永远不会发生一样进行优化

因此,如果发生这种可能的溢出,几乎无法预测编译器会生成什么样的程序。

  • 可能的输出是编译器忽略了分配,因为它不可能发生
  • 可能的输出是结果值是 0 扩展或符号扩展(取决于它是否已知为正)并解释为无符号整数。您可能会得到从 0size_t(-1) 的任何内容,因此可能分配的内存太少或太多,甚至无法分配,...
  • ...

未定义行为 => 所有赌注都关闭

【讨论】:

  • 感谢您提供一些实际可能的行为示例,而不是谈论“鼻恶魔”和“格式化硬盘驱动器”等等。
  • @Kyle,鼻恶魔当然是虚构的,但这种错误可能会格式化硬盘驱动器,或者如果它发生在导弹控制系统上则可以发射导弹! (malloc'ing 没有足够的字节,然后写超出)。
  • @MattMcNabb 当然,作为对具有未定义行为的代码的“合理”编译器响应之一的副作用。但是没有人会编写一个(主流)编译器,在看到具有未定义行为的语句时,插入代码设计以造成损害,只是因为根据标准这样做在技术上是允许的。因此,我认为,考虑编译器实际上可能生成什么样的代码是很有价值的;不过,我发现人们经常避免与 cmets 进行这些对话,大意是这无关紧要,因为任何代码都会符合要求。
  • @KyleStrand:在 gcc 中,如果 x*y 超过 0x7FFFFFFF,类似 unsigned mulMod65535(unsigned short x, unsigned short y) { return (x*y) & 0xFFFF;} 的调用代码可能会产生奇怪的副作用,但 gcc 的作者并不认为这是一个问题。 C 标准的作者在已发布的基本原理中描述了他们期望大多数当前实现如何在产生 INT_MAX+1u 和 UINT_MAX 之间的数值的整数表达式被强制为 unsigned 的情况下采取行动,但是是否真的以这种方式行事的问题是实施质量问题。
【解决方案2】:

一旦整数溢出,使用它的值会导致未定义的行为。根据标准,在溢出后使用int 的结果的程序是无效的——基本上,所有关于它的行为的赌注都被取消了。

考虑到这一点,让我们看看在负数以二进制补码表示形式存储的计算机上会发生什么。当你在这样的计算机上添加两个大的 32 位整数时,如果溢出,你会得到一个否定的结果。

但是,根据 C++ 标准,malloc 的参数类型,即size_t, is always unsigned。当您将负数转换为无符号数时,它会进行符号扩展 (see this answer for a discussion and a reference to the standard),这意味着原始的最高有效位(对于所有负数为 1)设置在前 32 位未签名的结果。

因此,您得到的是第三种情况的修改版本,除了“通配符位#”它一直到顶部都有一个。结果是一个巨大的无符号数(大约 16 个exbibytes 左右);自然malloc 分配不了那么多内存。

【讨论】:

  • 您的推理基于以下假设:溢出后int 为负数,但情况不一定如此。它可能是1,然后malloc 将准确分配1 字节......它实际上是崩溃(和漏洞)的常见来源。
  • @MatthieuM。 OP 提到他已经对此进行了测试,而malloc 分配失败,让他相信它是“1 或 3”。这就是为什么我有理由确定他得到的是负数。
  • 不清楚 OP 测试了什么,仅仅因为他曾经得到一个负数,一旦扩展产生一个巨大的数字并不意味着它会总是 就这样发生。
  • 我不确定是否“使用”溢出的整数值。如果将unsigned x 添加到已知为INT_MAXint n,编译器应该能够推断出x 必须为零,无论您是否再次使用n
  • 接受这个答案,因为它both给出了一个一般性的答案,强调了溢出的未定义行为并且它解释了这个特定的原因。
【解决方案3】:

因此,如果我们有特定的代码示例、特定的编译器和平台,我们可能可以确定编译器在做什么。这是Deep C 中采用的方法,但即便如此,它也可能无法完全预测,这是未定义行为的标志,概括未定义行为并不是一个好主意。

我们只需要看看gcc 文档中的建议,看看它会变得多么混乱。该文档在integer overflow 上提供了一些很好的建议,其中说:

在实践中,许多可移植的 C 程序假定有符号整数溢出使用二进制补码算法可靠地回绕。然而,C 标准规定程序行为在溢出时是未定义的,并且在某些情况下,C 程序无法在某些现代实现上工作,因为它们的溢出没有像作者预期的那样回绕。

在小节 Practical Advice for Signed Overflow Issues 中说:

理想情况下,最安全的方法是完全避免有符号整数溢出。[...]

归根结底,这是未定义的行为,因此在一般情况下是不可预测的,但在gcc 的情况下,在Integer 的实现定义部分中说整数溢出会环绕:

为了转换为宽度为 N 的类型,该值以 2^N 为模减少到该类型的范围内;没有发出信号。

但在他们关于整数溢出的建议中,他们解释了optimization can cause problems with wraparound

编译器有时会生成与环绕整数运算不兼容的代码。

所以这很快就会变得复杂。

【讨论】:

  • 编译器可以假设不会发生未定义的行为,因此不会发生 32 位整数溢出。所以它可以生成任何代码,只要没有溢出,就会生成正确的结果。一种可能性是将 x、y 都转换为有符号的 64 位,添加为 64 位,然后解释为无符号。只要没有 32 位溢出,这是正确的,因此是合法的。当然,不同的代码给出不同的结果也是合法的。
  • @gnasher729 这基本上就是我所说的
猜你喜欢
  • 2021-06-08
  • 1970-01-01
  • 2015-04-02
  • 2016-01-22
  • 2022-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多