C和C ++标准都很好地定义了无符号整数溢出。例如,C99标准(§6.2.5/9
)指出
涉及无符号操作数的计算永远不会溢出,因为无法用所得的无符号整数类型表示的结果的模数要比该所得类型可以表示的最大值大一模。
但是,两个标准都指出有符号整数溢出是未定义的行为。同样,根据C99标准(§3.4.3/1
)
未定义行为的一个示例是整数溢出时的行为
是否存在这种差异的历史原因(甚至更好!)是技术原因?
MAX_INT+1 == -0
而在两个补这将是INT_MIN
C和C ++标准都很好地定义了无符号整数溢出。例如,C99标准(§6.2.5/9
)指出
涉及无符号操作数的计算永远不会溢出,因为无法用所得的无符号整数类型表示的结果的模数要比该所得类型可以表示的最大值大一模。
但是,两个标准都指出有符号整数溢出是未定义的行为。同样,根据C99标准(§3.4.3/1
)
未定义行为的一个示例是整数溢出时的行为
是否存在这种差异的历史原因(甚至更好!)是技术原因?
MAX_INT+1 == -0
而在两个补这将是INT_MIN
Answers:
历史上的原因是,大多数C实现(编译器)都使用最容易用其整数表示实现的溢出行为。C实现通常使用与CPU使用相同的表示形式-因此,溢出行为遵循CPU使用的整数表示形式。
在实践中,只有符号值的表示会根据实现方式而有所不同:一个人的补码,两个人的补码,符号幅度。对于无符号类型,标准没有理由允许变化,因为只有一个明显的二进制表示形式(标准仅允许二进制表示形式)。
相关报价:
C99 6.2.6.1:3:
存储在无符号位域中的值和无符号字符类型的对象应使用纯二进制表示法表示。
C99 6.2.6.2:2:
如果符号位为1,则该值应通过以下方式之一进行修改:
—符号位0的对应值被取反(符号和幅度);
—符号位的值为-(2 N)(二进制补码);
—符号位的值为-(2 N -1)(一个补码)。
如今,所有处理器都使用二进制补码表示法,但带符号的算术溢出仍未定义,并且编译器制造商希望其保持未定义状态,因为他们使用这种不确定性来帮助优化。例如,请参阅Ian Lance Taylor的博客文章或Agner Fog的投诉,以及他的错误报告的答案。
除了Pascal的好答案(我确信这是主要动机)之外,某些处理器还可能在有符号整数溢出时引起异常,如果编译器不得不“安排其他行为”,那么这当然会引起问题(例如,使用额外的指令检查潜在的溢出情况,并在这种情况下进行其他计算)。
还值得注意的是,“未定义的行为”并不意味着“不起作用”。这意味着在这种情况下,允许实现执行任何操作。这包括做“正确的事情”以及“报警”或“崩溃”。假定可能相对容易定义(在本例中是这样),大多数编译器将在可能的情况下选择“做正确的事”。但是,如果您在计算中存在溢出,那么重要的是要了解实际结果,并且编译器可能会执行超出您期望的操作(这可能取决于编译器版本,优化设置等)。 。
int f(int x) { return x+1>x; }
进行优化后立即向您显示。GCC和ICC使用默认选项将上述内容优化为return 1;
。
int
溢出时会根据优化级别给出不同的结果,请参阅ideone.com/cki8nM。我认为这表明您的答案给出了错误的建议。
首先,请注意,C11 3.4.3像所有示例和脚注一样,不是规范性文字,因此与引用无关!
指出整数和浮点数溢出是未定义行为的相关文本是这样的:
C11 6.5 / 5
如果在表达式的求值过程中发生异常情况(即,如果未在数学上定义结果或该结果不在其类型的可表示值范围内),则该行为不确定。
可以在以下位置找到有关无符号整数类型的行为的明确说明:
C11 6.2.5 / 9
有符号整数类型的非负值范围是相应的无符号整数类型的子范围,并且每种类型中相同值的表示形式相同。涉及无符号操作数的计算永远不会溢出,因为无法用所得的无符号整数类型表示的结果的模数要比该所得的类型可以表示的最大值大一模。
这使无符号整数类型成为一种特殊情况。
还要注意,如果将任何类型转换为带符号类型,并且旧值无法再表示,则会出现异常。尽管可能会引发信号,但该行为仅是实现定义的。
C11 6.3.1.3
6.3.1.3有符号和无符号整数
将具有整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则该值不变。
否则,如果新类型是无符号的,则通过重复添加或减去比新类型可表示的最大值多一个值来转换该值,直到该值在新类型的范围内为止。
否则,将对新类型进行签名,并且无法在其中表示值;结果是实现定义的,还是引发实现定义的信号。
除了其他问题所提到的,具有无符号数包,使无符号整数类型表现为抽象代数群(这意味着,除其他事项外,对于任何一对值X
和Y
,将存在一些其他的价值Z
,使得X+Z
意志,如果得到适当的投,等于)。如果无符号值仅仅是存储位置类型,而不是中间表达式类型(例如,如果不存在最大整数类型的无符号等效项,并且对无符号类型的算术运算的行为就好像首先将它们转换为较大的有符号类型,则存在对定义的包装行为的需求不是很大,但是很难在没有例如加法逆的类型中进行计算。Y
和Y-Z
意志,如果适当地投,等于X
这在回绕行为实际上有用的情况下很有用-例如,使用TCP序列号或某些算法(例如哈希计算)。在需要检测溢出的情况下,它也可能会有所帮助,因为执行计算并检查它们是否溢出通常比事前检查它们是否会溢出更容易,尤其是在计算涉及最大可用整数类型的情况下。
a+b-c
是一个循环中计算的,但b
和c
该循环内是恒定的,它可能是有帮助的移动的计算(b-c)
外循环,但这样做将要求中,其它的东西(b-c)
产生,当加入到一个值a
,将产生a+b-c
,这又需要c
具有加法逆。
(a+b)-c
等于等于a+(b-c)
的算术值b-c
在类型内是否可表示,无论的可能取值范围如何,替换都将有效(b-c)
。
定义无符号算术的另一个原因可能是因为无符号数形成的整数模2 ^ n,其中n是无符号数的宽度。无符号数字只是使用二进制数字而不是十进制数字表示的整数。在模量系统中执行标准操作是众所周知的。
OP的引用引用了这一事实,但同时也强调了这样一个事实,即只有一种明确的逻辑方式可以表示二进制无符号整数。相反,带符号的数字通常使用二进制补码表示,但如标准(第6.2.6.2节)中所述,其他选择也是可能的。
二进制补码表示允许某些操作以二进制格式更有意义。例如,递增负数与正数相同(在溢出条件下预期)。对于有符号和无符号数字,计算机级别的某些操作可以相同。但是,在解释这些运算的结果时,有些情况没有意义-正向和负向溢出。此外,溢出结果根据基础签名表示而有所不同。