我们知道,signed integer overflow is undefined behavior。但是 C++11 cstdint
文档中有一些有趣的东西:
有符号整数类型,宽度分别为 8、16、32 和 64 位,没有填充位,负值使用 2 的补码(仅在实现直接支持该类型时提供)
这是我的问题:既然标准明确规定 int8_t
、int16_t
、int32_t
和 int64_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 位的有符号整数类型。
这些类型的溢出仍然是未定义的行为吗?
是的。根据 C++11 标准的第 5/4 段(关于一般的任何表达式):
如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。 [...]
对这些有符号类型使用二进制补码表示这一事实并不意味着在评估这些类型的表达式时使用算术模 2^n。
另一方面,关于无符号算术,标准明确规定(第 3.9.1/4 段):
声明为无符号的无符号整数应遵守算术模 2^n 的定律,其中 n 是该特定整数大小的值表示中的位数
这意味着无符号算术运算的结果始终是“数学定义的”,并且结果始终在可表示的范围内;因此,5/4 不适用。脚注 46 解释了这一点:
46) 这意味着无符号算术不会溢出,因为不能由得到的无符号整数类型表示的结果以比得到的无符号整数类型可以表示的最大值大一的数字为模减少。
仅仅因为一个类型被定义为使用 2s 补码表示,它并没有遵循该类型中的算术溢出被定义。
有符号算术溢出的未定义行为用于启用优化;例如,编译器可以假设 if a > b
then a + 1 > b
也;这不适用于需要执行第二次检查的无符号算术,因为 a + 1
可能会回绕到 0
。此外,某些平台可以在算术溢出时生成陷阱信号(参见例如 http://www.gnu.org/software/libc/manual/html_node/Program-Error-Signals.html);该标准继续允许这种情况发生。
abs
等函数。对于那些工作时的操作,它不需要比签名算术更多的指令。
(int)(x+y)>z
将比较一个包装的结果),并且还允许程序员在代码可以接受的情况下编写 x+y>z
如果没有其他副作用,则在溢出的情况下产生 0 或 1。如果 0 或 1 是同样可接受的结果,则让程序员编写而不是 (long)x+y>z
或 (int)((unsigned)x+y)>z
将允许编译器选择后一个函数中的任何一个在任何给定的上下文中更便宜[每个在某些情况下都会更便宜案例]。
我敢打赌。
从标准文档(pg.4 和 5):
1.3.24 未定义行为 本国际标准对其没有要求的行为 [注:当本国际标准省略任何明确的行为定义或程序使用错误构造或错误数据时,可能会出现未定义行为。允许的未定义行为的范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(发出的诊断消息)。许多错误的程序结构不会产生未定义的行为;他们需要被诊断出来。--结束注]