如果数字太大,会溢出到下一个存储位置吗?


30

我一直在审查C编程,但有几件事困扰着我。

让我们以以下代码为例:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

我知道一个int可以容纳正数2,147,483,647的最大值。因此,通过遍历它,是否“溢出”到下一个内存地址,从而导致元素2在该地址处显示为“ -2147483648”?但这并没有真正的意义,因为在输出中它仍然说下一个地址保持值为4,然后是5。如果该数字已溢出到下一个地址,则不会改变存储在该地址的值?

我隐约记得在MIPS Assembly中进行编程时,看着地址在程序执行过程中逐步改变了值,分配给这些地址的值会改变。

除非我没有记错,否则这是另一个问题:如果分配给特定地址的数字大于类型(如myArray [2]中的类型),那么它是否不影响存储在后续地址中的值?

示例:我们在地址0x10010000处有int myNum = 40亿。当然,myNum不能存储40亿,因此它在该地址显示为负数。尽管不能存储这么大的数字,但它对存储在后续地址0x10010004上的值没有影响。正确?

内存地址仅具有足够的空间来容纳某些大小的数字/字符,如果大小超出限制,则其表示方式将有所不同(例如尝试将40亿存储到int中,但它将显示为负数),并且因此,它不会影响下一个地址中存储的数字/字符。

对不起,如果我过分。从那以后,我整天都在闹大闹鬼。


10
您可能会对字符串溢出感到困惑。
罗比·迪

19
作业:修改一个简单的CPU,使其不会溢出。您会看到逻辑变得更加复杂,所有这些都是“特征”,它可以确保无处不在的安全漏洞,而不是一开始就有用。
phihag

4
如果您确实需要非常大的数字,则可以使用数字表示形式,以增加用于容纳大数字的内存量。处理器本身无法做到这一点,它不是C语言的功能,但是可以实现它的库-通用的C库是GNU Multiple Precision算法库。该库必须管理内存来存储数字,这在算术之上具有较高的性能成本。许多语言都内置了这种东西(无法避免成本)。
Steve314 '16

1
编写一个简单的测试,我不是C程序员,而是类似C的人int c = INT.MAXINT; c+=1;,看看c发生了什么。
JonH

2
@JonH:问题是未定义行为中溢出。AC编译器可能会发现该代码,并推断出它是不可访问的代码,因为它无条件溢出。由于无法访问的代码无关紧要,因此可以将其消除。最终结果:无代码。
MSalters

Answers:


48

不,不是的。在C语言中,变量具有一组固定的内存地址可以使用。如果您正在使用4字节的系统上工作ints,并且将int变量设置为2,147,483,647然后添加1,则该变量通常包含-2147483648。(在大多数系统上。行为实际上是未定义的。)不会修改其他内存位置。

本质上,编译器不允许您为该类型分配太大的值。这将产生一个编译器错误。如果使用大小写将其强制使用,则该值将被截断。

以位方式查看,如果类型只能存储8位,并且您尝试1010101010101用大小写将值强制放入其中,则最终将以低8位或结束01010101

在您的示例中,无论您做什么myArray[2]myArray[3]都将包含“ 4”。没有“溢出”。您正在尝试放置超过4字节的内容,这只会使高端的所有内容失去优势,而剩下的4字节会保留下来。在大多数系统上,这将导致-2147483648

从实际的角度来看,您只想确保这种情况永远不会发生。这些溢出通常会导致难以解决的缺陷。换句话说,如果您认为您的所有价值都可能达到数十亿美元,请不要使用int


52
如果您在具有4字节整数的系统上工作,并且将int变量设置为2,147,483,647,然后加1,则该变量将包含-2147483648。=> ,这是未定义的行为,因此它可能会循环播放或完全执行其他操作;我见过编译器根据没有溢出的情况优化检查,例如出现无限循环……
Matthieu M.16。Jan

抱歉,是的,您是对的。我应该在其中添加“通常”。

语言角度来说,@ MatthieuM是对的。关于在给定系统上的执行(这就是我们在这里所说的),这绝对是胡说八道。
hobbs

@hobbs:问题在于,当编译器由于未定义行为而使程序崩溃时,实际上运行程序确实会产生意外行为,实际上与覆盖内存相当。
Matthieu M.

24

有符号整数溢出是未定义的行为。如果发生这种情况,则您的程序无效。编译器不需要为您检查此操作,因此它可能会生成可执行合理的可执行文件,但不能保证会执行。

但是,无符号整数溢出是明确定义的。它将以模UINT_MAX + 1包装。未被变量占用的内存不会受到影响。

另请参阅https://stackoverflow.com/q/18195715/951890


有符号整数溢出与无符号整数溢出一样明确。如果单词有$ N $位,则有符号整数溢出的上限为$$ 2 ^ {N-1} -1 $$(它绕到$ -2 ^ {N-1} $),而无符号整数溢出的上限为$$ 2 ^ N-1 $$(它绕到$ 0 $处)。相同的加减法机制,可表示的数字范围($ 2 ^ N $)的大小相同。只是溢出的另一个边界。
罗伯特·布里斯托

1
@ robertbristow-johnson:不符合C标准。
沃恩·卡托

好吧,有时标准是不合时宜的。看一下SO参考,有一条评论直接击中了它:“不过,重要的是,现代世界中没有使用2的补码符号算术以外的任何体系结构。语言标准仍然允许实现例如PDP-1就是纯粹的历史
产物。–

我想它不是 C标准,但我想可能有一个不使用常规二进制算术的实现int。我想他们可以使用格雷码BCDEBCDIC。不知道为什么有人会设计硬件来使用格雷代码或EBCDIC进行算术运算,但是再说一次,我不知道为什么有人会unsigned使用二进制代码并int使用除2的补码之外的任何符号进行签名。
罗伯特·布里斯托

14

因此,这里有两件事:

  • 语言级别:C的语义是什么
  • 计算机级别:您使用的程序集/ CPU的语义是什么

在语言级别:

在C中:

  • 上溢和下溢定义为无符号整数的模运算,因此它们的值“循环”
  • 溢和下溢是未定义行为签署整数,所以任何事情都有可能发生

对于那些想要“什么都可以”的示例,我已经看到:

for (int i = 0; i >= 0; i++) {
    ...
}

变成:

for (int i = 0; true; i++) {
    ...
}

是的,这是合法的转变。

这意味着由于一些奇怪的编译器转换,确实存在在溢出时覆盖内存的潜在风险。

注意:在Clang或gcc上,-fsanitize=undefined在Debug中使用以激活Undefined Behavior Sanitizer,它将在有符号整数的下溢/上溢时中止。

或这意味着您可以通过使用操作结果索引(未选中)到数组中来覆盖内存。不幸的是,在没有下溢/上溢检测的情况下,这种可能性更大。

注意:在Clang或gcc上,-fsanitize=address在Debug中使用可激活Address Sanitizer,后者将在越界访问时中止。


在机器级别

这实际上取决于您使用的汇编指令和CPU:

  • 在x86上,ADD将在溢出/下溢时使用2补码,并设置OF(溢出标志)
  • 在未来的Mill CPU上,将有4种不同的溢出模式Add
    • 模:2补模
    • 陷阱:生成陷阱,停止计算
    • 饱和:值在下溢时停留在最小值,在上溢时停留在最大值
    • 双倍宽度:结果在双倍宽度寄存器中生成

请注意,无论是发生在寄存器还是内存中,CPU都不会在溢出时覆盖内存。


最后三种模式是否已签名?(与第一个无关紧要,因为它是2的补码。)
Deduplicator

1
@Deduplicator:根据《 Mill CPU编程模型简介》,有符号加法和无符号加法有不同的操作码;我希望这两个操作码都支持4种模式(并且能够在各种位宽和标量/向量上运行)。再说一遍,它现在只是蒸气硬件;)
Matthieu M.

4

为了进一步@StevenBurnap的答案,发生这种情况的原因是由于计算机在计算机级别的工作方式。

您的阵列存储在内存中(例如,RAM中)。当执行算术运算时,会将存储器中的值复制到执行算术的电路的输入寄存器(ALU:算术逻辑单元)中,然后对输入寄存器中的数据进行运算,得出结果在输出寄存器中。然后,将结果复制到内存中正确地址的内存中,而不会影响其他内存区域。


4

首先(假定为C99标准),您可能需要包括<stdint.h>标准标头,并使用在那里定义的一些类型,尤其int32_t是正好是32位有符号整数或uint64_t正好是64位无符号整数,依此类推。int_fast16_t由于性能原因,您可能想要使用类似的类型。

阅读其他答案,说明无符号算术永远不会溢出(或溢出)到相邻的内存位置。谨防未定义行为签署溢出。

然后,如果您需要计算正好庞大的整数(例如,要计算其乘数2的所有10568数字均为1000),则需要bigints又名任意精度数字(或bignums)。有效的bigint算术算法非常聪明,通常需要使用专门的机器指令(例如,如果处理器有带进位的附加词)。因此,我强烈建议在这种情况下使用一些现有的 bigint库,例如GMPlib

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.