在C ++中,签名类型的溢出是未定义的行为。以下示例是否也是未定义的行为?
#include <limits.h>
int f() {
int a = INT_MAX;
int b = -1;
return a + b;
}
这不是数学上下文中的溢出,但CPU可能会看到它
add 0x7fffffff 0xffffffff
。
答案 0 :(得分:10)
您提供的示例不是溢出。
来自维基百科(https://en.wikipedia.org/wiki/Integer_overflow):
...算术运算尝试时会发生整数溢出 创建一个超出范围的数值 用给定的位数表示 - 要么大于 最大或低于最小可表示值。
INT_MAX +( - 1)不在int类型可表示的范围之外,并且结果已定义。
答案 1 :(得分:8)
由于INT_MAX + (-1)
的结果在int
的可表示值范围内,因此定义明确
您应该停止将语言视为装配或机器代码上的薄层。
关于程序的未定义,没有CPU,只有程序运行的abstract machine。
答案 2 :(得分:1)
在二进制补码系统中,进位和溢出是不同的概念。许多这样的系统被设计为支持多字算术。如果将值解释为无符号的算术和将超出类型的范围,则将报告进位。在任何计算之后都会报告溢出,其中进位到高位与进位不同,但只有在计算高位字节后才有意义。
例如,在8位系统上,“int”通常为两个字节,INT_MAX为0x7F:0xFF,-1为0xFF:FF。通过添加两个较低字节来执行加法,然后使用较低字节的进位添加两个高位字节。
将0xFF添加到0xFF会产生0xFE并且进位但没有溢出(进出高位进入和退出)。
将0x7F添加到0xFF会产生0x7E,没有进位或溢出(既不进入也不进出高位)。
这样的细节通常对C程序员来说无关紧要,因为编译器通常不提供访问溢出标志的方法,也没有任何方法可以确保以使得标志在任何特定点有意义的方式执行计算。代码。尽管如此,C89理由还是指出:
C代码可以是非便携式的 虽然它努力为程序员提供编写真正可移植程序的机会,但C89委员会并不想强迫程序员写作 可以轻松地排除使用C作为“高级汇编程序”:编写机器特定代码的能力是C的优势之一。正是这一原则在很大程度上推动了严格符合程序和符合程序之间的区别(§4)。
请注意,顺便说一句,将小型无符号类型提升为签名int
的理由强烈暗示C89的作者期望某事
如下所示在普通平台上应该是安全的:
unsigned mul_mod_65536(unsigned short x, unsigned short y)
{ return (x*y) & 0xFFFF; }
尽管如此,当通过过度“聪明”的优化编译器(如gcc)进行处理时,此类代码在常见的32位平台上处理时可能会出现故障。没有理由为这样的函数生成的机器代码应该关心x*y
的值是否超过INT_MAX
,因为结果的高位将被切断,低位将是无论如何都是正确的。但是,在某些情况下,如果gcc知道例如y将为65535,它可能决定由于x*y
“不能”超过INT_MAX,x
“不能”超过0x8000。标准的作者可能不想排除C被用作高级汇编程序的可能性,但这并不意味着编译器编写者会有这种感觉。