无符号整数溢出在C和c++标准中都有很好的定义。例如,C99标准(§6.2.5/9)指出
涉及无符号操作数的计算永远不会溢出, 因为不能由结果无符号整数类型表示的结果为 对比最大值大1的数取模 由结果类型表示。
然而,这两个标准都指出有符号整数溢出是未定义的行为。同样,从C99标准(§3.4.3/1)
未定义行为的一个例子是对整数溢出的行为
造成这种差异的原因是历史原因还是技术原因?
无符号整数溢出在C和c++标准中都有很好的定义。例如,C99标准(§6.2.5/9)指出
涉及无符号操作数的计算永远不会溢出, 因为不能由结果无符号整数类型表示的结果为 对比最大值大1的数取模 由结果类型表示。
然而,这两个标准都指出有符号整数溢出是未定义的行为。同样,从C99标准(§3.4.3/1)
未定义行为的一个例子是对整数溢出的行为
造成这种差异的原因是历史原因还是技术原因?
当前回答
历史原因是大多数C实现(编译器)只是使用它所使用的整数表示形式最容易实现的溢出行为。C实现通常使用与CPU相同的表示法-因此溢出行为跟随CPU使用的整数表示法。
在实践中,只有符号值的表示形式可能会因实现的不同而不同:1的补数、2的补数、符号的大小。对于无符号类型,标准没有理由允许变化,因为只有一种明显的二进制表示(标准只允许二进制表示)。
相关报价:
C99 6.2.6.1:3:
存储在unsigned位字段中的值和unsigned char类型的对象应使用纯二进制表示法表示。
C99 6.2.6.2:2:
如果符号位为1,则该值应以以下方式之一进行修改: -与符号位0对应的值被否定(符号和幅度); -符号位的值为−(2N)(2的补码); -符号位的值为−(2N−1)(补码)。
现在,所有处理器都使用2的补数表示,但有符号算术溢出仍然未定义,编译器制作者希望它保持未定义,因为他们使用这种不确定性来帮助优化。例如Ian Lance Taylor的博客文章或Agner Fog的投诉,以及他的错误报告的答案。
其他回答
历史原因是大多数C实现(编译器)只是使用它所使用的整数表示形式最容易实现的溢出行为。C实现通常使用与CPU相同的表示法-因此溢出行为跟随CPU使用的整数表示法。
在实践中,只有符号值的表示形式可能会因实现的不同而不同:1的补数、2的补数、符号的大小。对于无符号类型,标准没有理由允许变化,因为只有一种明显的二进制表示(标准只允许二进制表示)。
相关报价:
C99 6.2.6.1:3:
存储在unsigned位字段中的值和unsigned char类型的对象应使用纯二进制表示法表示。
C99 6.2.6.2:2:
如果符号位为1,则该值应以以下方式之一进行修改: -与符号位0对应的值被否定(符号和幅度); -符号位的值为−(2N)(2的补码); -符号位的值为−(2N−1)(补码)。
现在,所有处理器都使用2的补数表示,但有符号算术溢出仍然未定义,编译器制作者希望它保持未定义,因为他们使用这种不确定性来帮助优化。例如Ian Lance Taylor的博客文章或Agner Fog的投诉,以及他的错误报告的答案。
除了Pascal的好答案(我相信这是主要的动机),也有可能一些处理器会在有符号整数溢出上引起异常,如果编译器不得不“安排另一种行为”(例如,使用额外的指令来检查潜在的溢出,并在这种情况下计算不同),这当然会导致问题。
It is also worth noting that "undefined behaviour" doesn't mean "doesn't work". It means that the implementation is allowed to do whatever it likes in that situation. This includes doing "the right thing" as well as "calling the police" or "crashing". Most compilers, when possible, will choose "do the right thing", assuming that is relatively easy to define (in this case, it is). However, if you are having overflows in the calculations, it is important to understand what that actually results in, and that the compiler MAY do something other than what you expect (and that this may very depending on compiler version, optimisation settings, etc).
首先,请注意,C11 3.4.3与所有示例和脚注一样,不是规范文本,因此与引用无关!
说明整数和浮点数溢出是未定义行为的相关文本是这样的:
C11 6.5/5
如果在评估过程中出现异常情况 表达式(即,如果结果不是数学上定义的或 不在其类型的可表示值范围内),即行为 是未定义的。
关于无符号整数类型行为的详细说明可以在这里找到:
C11 6.2.5/9
有符号整型的非负值的范围是子范围 对应的无符号整数类型的 每种类型中的相同值都是相同的。计算包括 无符号操作数永远不会溢出,因为结果不能溢出 由结果的无符号整数类型表示的是模数化简 比最大值大1的数 由结果类型表示。
这使得无符号整数类型成为一种特殊情况。
还要注意,如果任何类型被转换为有符号类型,并且旧的值不能再表示,则会出现异常。尽管可能会引发信号,但行为只是由实现定义的。
C11 6.3.1.3
6.3.1.3 Signed and unsigned integers When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
In addition to the other issues mentioned, having unsigned math wrap makes the unsigned integer types behave as abstract algebraic groups (meaning that, among other things, for any pair of values X and Y, there will exist some other value Z such that X+Z will, if properly cast, equal Y and Y-Z will, if properly cast, equal X). If unsigned values were merely storage-location types and not intermediate-expression types (e.g. if there were no unsigned equivalent of the largest integer type, and arithmetic operations on unsigned types behaved as though they were first converted them to larger signed types, then there wouldn't be as much need for defined wrapping behavior, but it's difficult to do calculations in a type which doesn't have e.g. an additive inverse.
这有助于在绕换行为实际有用的情况下—例如TCP序列号或某些算法,如哈希计算。它还可以在需要检测溢出的情况下提供帮助,因为执行计算并检查它们是否溢出通常比事先检查它们是否会溢出更容易,特别是当计算涉及可用的最大整数类型时。
也许无符号算术被定义的另一个原因是因为无符号数是整数对2^n模的形式,其中n是无符号数的宽度。无符号数是用二进制数字而不是十进制数字表示的整数。在模数系统中执行标准操作是很容易理解的。
OP的引用提到了这一事实,但也强调了这样一个事实:在二进制中表示无符号整数只有一种明确的逻辑方法。相比之下,有符号数通常使用2的补数表示,但也可以使用标准中描述的其他选择(第6.2.6.2节)。
Two's complement representation allows certain operations to make more sense in binary format. E.g., incrementing negative numbers is the same that for positive numbers (expect under overflow conditions). Some operations at the machine level can be the same for signed and unsigned numbers. However, when interpreting the result of those operations, some cases don't make sense - positive and negative overflow. Furthermore, the overflow results differ depending on the underlying signed representation.