在C ++中,有符号整数溢出仍然是未定义的行为吗?


82

众所周知,有符号整数溢出是未定义的行为。但是C ++ 11cstdint文档中有一些有趣的东西:

有符号整数类型,其宽度分别精确地为8、16、32和64位,没有填充位,并且对负值使用2的补码(仅在实现直接支持该类型时提供)

见链接

这里是我的问题:由于标准明确地说,对int8_tint16_tint32_tint64_t负数是2的补,还是溢出这些类型的未定义的行为吗?

编辑我检查了C ++ 11和C11标准,这是我发现的内容:

C ++ 11,第18.4.1节:

标头定义了与C标准中的7.20相同的所有函数,类型和宏。

C11,第7.20.1.1节:

typedef名称intN_t指定宽度为N,无填充位和二进制补码表示形式的带符号整数类型。因此,int8_t表示具有正好8位宽度的这种有符号整数类型。


14
永远不要忘记,C ++的唯一主要文档是标准。其他所有内容,甚至是像CppReference这样的Wiki,都是辅助来源。这并不意味着是错误的。只是不完全可靠。
Nicol Bolas

我希望它是UB,在C语言中没有针对这些类型的豁免,我不明白C ++为什么要添加一个。
丹尼尔·菲舍尔2013年


3
我有些困惑:句子“带符号整数类型中的动词分别是宽度分别精确地为8、16、32和64位,没有填充位并且对负值使用2的补码(仅在实现直接支持时提供)方式)?” 有点缺失吗?这是什么意思?
YSC 2015年

C ++ 11基于C99,而不是C11。但这无论如何都不重要
LF

Answers:


80

这些类型的溢出仍然是未定义的行为吗?

是。根据C ++ 11标准的第5/4段(关于任何一般表达):

如果在表达式的求值过程中,未在数学上定义结果或该类型的结果不在可表示值的范围内,则行为不确定。[...]

对于那些有符号的类型使用二进制补码表示形式的事实并不意味着在评估那些类型的表达式时使用算术模2 ^ n。

另一方面,关于无符号算术,该标准明确规定(第3.9.1 / 4段):

无符号整数,声明unsigned应当服从算术模的法律2 ^ N,其中n是比特的整数的特定大小的值表示的数目

这意味着无符号算术运算的结果始终是“数学定义的”,并且结果始终在可表示的范围内;因此5/4不适用。脚注46对此进行了解释:

46)这意味着无符号算术不会溢出,因为不能用所得的无符号整数类型表示的结果的模数要比所得的无符号整数类型可以表示的最大值的模数大。


1
本段还暗示未签名的溢出是未定义的,不是。
Archie

8
@Archie:不是真的,因为无符号值定义无符号范围。
Lightness Races in Orbit

3
@Archie:我想澄清,但基本上你从LightnessRacesinOrbit答案
安迪警车

1
实际上,是否定义了无符号溢出并不重要,如果由于模运算而导致它不能发生……
Aconcagua

1
有一些未签名的运算,其结果不是“数学定义的”-尤其是被零除-因此,您的措辞可能与该句中的含义不符。ITYM表示,如果对结果进行数学定义,则也用C ++定义。
Toby Speight

22

仅仅因为一个类型被定义为使用2s补码表示法,就不会跟随该类型的算术溢出被定义。

有符号算术溢出的未定义行为用于优化。例如,编译器可以假定,如果a > b然后a + 1 > b也; 这在无符号算术中并不成立,因为可能a + 1会绕到到,因此需要进行第二次检查0。而且,某些平台可以在算术溢出时生成陷阱信号(请参见例如http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html);该标准继续允许这种情况发生。


5
值得注意的是,许多人“担心”陷阱的可能性,但是编译器的假设实际上更加隐蔽(我希望在实现定义的行为和未定义的行为之间有一个类别的原因之一,与实现定义的行为不同这要求特定的实现以一致的文档形式执行某项操作,我想要一种“实现受限”的行为,该行为将要求实现指定由于某种结果而可能发生的一切(规范可能明确包含未定义的行为,但是。 。
supercat

3
...在可行的情况下,鼓励实施方式更加具体。在二进制补码自然会“换行”的硬件上,没有理智的理由让代码希望一个包装整数结果执行许多指令而试图在没有整数溢出的情况下执行计算,而该硬件只需一个或两个指令就可以执行。
2015年

1
@supercat实际上,想要包装结果的代码可以(在2的补码CPU上)仅将操作数转换为相应的无符号类型并执行操作(然后回退,以获取实现定义的值):这适用于加,减和乘法。唯一的问题是除法,取模以及诸如之类的函数abs。对于那些运作正常的操作,不需要更多的指示,而无需签署带符号的假肢。
Ruslan

@Ruslan:在代码需要精确包装的结果的情况下,对unsigned的强制转换会很丑陋,但不一定会生成额外的代码。更大的问题是需要快速识别“潜在有趣”候选者的代码,这将花费大部分时间拒绝不感兴趣的候选者。如果允许编译器自由地使用带符号的整数值任意保留或放弃额外的精度,但要求强制转换为整数类型,则截断任何这样的精度,这将使通过溢出UB实现的大多数有用的优化成为可能。 ,...
超级猫

...但是将允许需要精确包装的代码使用一个强制转换而不是两个强制转换(例如(int)(x+y)>z,比较包装的结果),并且还允许程序员编写x+y>z代码,以防万一代码产生0或1如果没有其他副作用,则溢出。如果0或1是同等可接受的结果,则让程序员写那个而不是其中一个,(long)x+y>z或者(int)((unsigned)x+y)>z让编译器选择在任何给定上下文中哪个后者的价格便宜[在某些情况下每个价格都便宜]。
超级猫

1

我敢打赌。

从标准文档(第4和5页)中:

1.3.24未定义的行为

本国际标准对此没有要求的行为

[注意:当本国际标准省略行为的任何明确定义或程序使用错误的构造或错误的数据时,可能会出现未定义的行为。允许的未定义行为的范围从完全忽略具有无法预测结果的情况到在翻译或程序执行过程中以环境特征的书面方式记录的行为(带有或不带有诊断消息)到终止翻译或执行(带有发布)诊断消息)。许多错误的程序构造不会引起未定义的行为。必须对其进行诊断。-尾注]

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.