为什么无符号整数溢出定义了行为但是有符号整数溢出不是?

时间:2013-08-12 20:04:18

标签: c++ c undefined-behavior integer-overflow

C和C ++标准都很好地定义了无符号整数溢出。例如,C99 standard§6.2.5/9)状态

  

涉及无符号操作数的计算永远不会溢出,   因为无法用结果无符号整数类型表示的结果是   减少模数可以是最大值的数字   由结果类型表示。

但是,两种标准都指出有符号整数溢出是未定义的行为。同样,来自C99标准(§3.4.3/1

  

未定义行为的一个示例是整数流上的行为

是否存在这种差异的历史或(甚至更好!)技​​术原因?

5 个答案:

答案 0 :(得分:146)

历史原因是大多数C实现(编译器)只使用了最容易使用它所使用的整数表示来实现的溢出行为。 C实现通常使用CPU使用的相同表示 - 因此溢出行为遵循CPU使用的整数表示。

在实践中,只有符号值的表示可能根据实现而有所不同:补码,二补码,符号幅度。对于无符号类型,标准没有理由允许变化,因为只有一个明显的二进制表示(标准只允许二进制表示)。

相关引言:

C99 6.2.6.1:3

  

存储在无符号位域中的值和unsigned char类型的对象应使用纯二进制表示法表示。

C99 6.2.6.2:2

  

如果符号位为1,则应以下列方式之一修改该值:

     

- 符号位0的相应值被否定(符号和幅度);

     

- 符号位的值为 - (2 N )(二的补码);

     

- 符号位的值为 - (2 N - 1)(一个补码)。


如今,所有处理器都使用了两个补码表示,但是带符号的算术溢出仍未定义,编译器制造商希望它保持未定义,因为它们使用这种未定义来帮助优化。例如,参见Ian Lance Taylor的这个blog post或者Agner Fog的complaint,以及他的错误报告的答案。

答案 1 :(得分:13)

除了Pascal的好答案(我确信这是主要动机)之外,某些处理器也可能导致有符号整数溢出异常,如果编译器必须“安排另一个处理器”,这当然会引起问题行为“(例如,使用额外的指令来检查潜在的溢出并在这种情况下以不同的方式计算)。

值得注意的是,“未定义的行为”并不意味着“不起作用”。这意味着允许实现在那种情况下做任何它喜欢的事情。这包括做“正确的事”以及“报警”或“崩溃”。大多数编译器在可能的情况下会选择“做正确的事情”,假设相对容易定义(在这种情况下,它是)。但是,如果计算中出现溢出,重要的是要了解实际导致的结果,并且编译器可以执行除您期望之外的其他操作(并且这可能非常依赖于编译器版本,优化设置等) 。

答案 2 :(得分:9)

首先,请注意C11 3.4.3,与所有示例和脚注一样,不是规范性文本,因此与引用无关!

相关文本指出整数和浮点数的溢出是未定义的行为是这样的:

C11 6.5 / 5

  

如果在评估过程中出现异常情况   表达式(即,如果结果未在数学上定义或   不在其类型的可表示值的范围内),行为   未定义。

有关无符号整数类型行为的说明,请参见:

C11 6.2.5 / 9

  

有符号整数类型的非负值范围是子范围   相应的无符号整数类型,以及表示形式   每种类型的相同值是相同的。涉及的计算   无符号操作数永远不会溢出,因为结果不可能   由结果无符号整数类型表示的模数减少   比最大值大1的数字   由结果类型表示。

这使得无符号整数类型成为一种特殊情况。

另请注意,如果任何类型转换到签名类型并且无法再表示旧值,则会出现异常。然后,行为仅仅是实现定义的,尽管可能会引发信号。

C11 6.3.1.3

  

6.3.1.3有符号和无符号整数

     

当一个值带整数时   如果是,则将类型转换为_Bool以外的另一个整数类型   value可以用新类型表示,它不变。

     

否则,如果新类型是无符号的,则转换为   重复加或减一个以上的最大值   可以用新类型表示,直到值在范围内   新型。

     

否则,新类型已签名且值为   不能代表它;结果是   实现定义或实现定义的信号被引发。

答案 3 :(得分:6)

除了提到的其他问题之外,使用无符号数学换行使得无符号整数类型表现为抽象代数组(除其他外,对于任何一对值XY,将存在一些其他值Z,如果正确投射,X+Z将等于YY-Z,如果正确投射,将等于X)。如果无符号值只是存储位置类型而不是中间表达式类型(例如,如果没有最大整数类型的无符号等价物,并且对无符号类型的算术运算表现得好像它们首先将它们转换为更大的有符号类型,那么那里不需要定义的包装行为,但是在没有例如加法逆的类型中进行计算是很困难的。

这有助于环绕行为实际有用的情况 - 例如使用TCP序列号或某些算法,例如哈希计算。它也可能在需要检测溢出的情况下有所帮助,因为执行计算并检查它们是否溢出通常比预先检查它们是否会溢出更容易,特别是如果计算涉及最大可用整数类型。

答案 4 :(得分:1)

为什么定义无符号算术的另一个原因是因为无符号数形成模2 ^ n的整数,其中n是无符号数的宽度。无符号数字只是使用二进制数字而不是十进制数字表示的整数。在模数系统中执行标准操作是很好理解的。

OP的引用引用了这个事实,但也突出了这样一个事实:只有一种明确的逻辑方式来表示二进制中的无符号整数。相比之下,有符号数通常用二进制补码表示,但其他选择也是可能的,如标准(第6.2.6.2节)所述。

二进制补码表示允许某些操作以二进制格式更有意义。例如,递增负数与正数相同(在溢出条件下预期)。对于有符号和无符号数字,机器级别的某些操作可以相同。但是,在解释这些操作的结果时,有些情况没有意义 - 正面和负面溢出。此外,溢出结果根据底层签名表示而不同。