当整数溢出发生时,有符号和无符号整数的行为差异。

4

阅读维基百科上关于 整数溢出 的文章

我不太明白有符号整数溢出会导致未定义的行为,而无符号整数溢出会导致环绕。它们的行为有何差异?

另一个问题:一般的编程语言是否有任何防止整数溢出的保障?


1
在C++中,无符号整数永远不会溢出。 - R. Martinho Fernandes
2
@R.MartinhoFernandes: 你的意思是什么? - NPE
3
技术术语中,无符号整数的包装行为对于超出范围的结果并非技术上的溢出。 - Daniel Fischer
1
C++ 中无符号整数的操作遵循算术模 2^n 的规则,其中 n 是类型中位数的数量。这意味着没有溢出。 - R. Martinho Fernandes
4
那可能也是一个原因。C语言支持二进制补码、反码和原码,所以像无符号整数的二进制补码解释一样指定溢出绕回,是不可能的,因为在反码和原码中无法表示“-2^(WIDTH-1)”。 - Daniel Fischer
显示剩余8条评论
4个回答

2
主要原因是C和C++语言规范允许实现使用以下三种不同的有符号整数表示:
1. 补码 2. 反码 3. 原码
如果语言规范在有符号溢出的情况下强制执行某些特定行为(即优先选择上述三种表示法中的一种),则基于两个非优选表示法的平台将不得不实现“繁重”的有符号整数算术运算。这是必要的,因为这些平台的自然机器级行为与语言标准所需的行为不匹配。这些实现将不得不不断地观察有符号溢出并调整结果以符合标准要求。
这将严重降低使用非优选有符号表示的平台上有符号整数算术的性能,这在C和C++等语言中完全不可接受,因为它们旨在在诸如整数算术之类的基本操作方面尽可能接近底层硬件。
行为未定义(而不是“未指定”)的原因是因为一些平台故意在整数算术期间发生有符号溢出时生成硬件异常。请注意,此行为仅对通常由机器执行的算术操作未定义。对于值转换,有符号溢出不会产生未定义行为(行为实际上是实现定义的)。
至于无符号类型,它们在所有平台上都以相同的方式表示,这意味着要求在所有平台上具有一致的行为不是问题。在几乎所有已知的二进制硬件平台上,与“模2 ^宽度”行为概念上匹配的环绕是自然行为。

1
  1. 因为语言是这样定义的。它允许更容易地在更多种类的硬件上(例如带有饱和算术的 DSP)开发符合规范的实现。

  2. 取决于编程语言。一些硬件支持,你也许可以在程序中利用它们。


1

C/C++ 中关于整数溢出的方法是提供在您所使用的机器上最快的行为,因此在某些机器上(假设为 16 位有符号整数):

32766 + 2 == -32768

但在其他一些机器上则为:

32766 + 2 == 32767

对于其他机器,您可能会遇到陷阱值或 CPU 执行的任何操作。

请注意,Java 已经完美地定义了整数溢出,以实现“编写一次,随处运行”。

至于无符号整数,它们的大多数应用程序都是位掩码、位字段和数字操作(模运算、标识符)——正是您不希望它们未定义的操作。

一些编程语言具有这样的安全措施,而一些则没有:

  • Python 3 会自动将溢出的值转换为 long 类型(任意大的整数)。

  • 在 C/C++ 中,您必须自己检查溢出条件,climits(C)和 limits(C++)头文件已经为每种类型定义了最大和最小值。

  • 在 x86 汇编中编程——FLAGS 和 EFLAGS 寄存器中有 CF(进位标志)用于无符号和 OF(溢出标志)用于有符号,以检查何时发生溢出。

许多编程语言也有任意精度类型,以防止溢出,但是操作会更慢,因为这些变量在理论上可以达到你的内存大小。


0
在Java中,您只有无符号的intlong值,并且它们的行为在任何地方运行时都是一致的。如果您将1添加到Integer.MAX_VALUE,则会得到Integer.MIN_VALUE(它会回绕),如果您从Long.MIN_VALUE减去1,则会得到Long.MAX_VALUE。
因此,我不知道为什么其他语言中无符号值的行为会是未定义的。

1
Java中的int和long都是有符号整数类型,需要表现得好像它们是2的补码。 - Patricia Shanahan
Java中唯一的无符号整数类型是char,它是16位的。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接