我在 C 中使用整数,试图更多地探索溢出发生的时间和方式。
我注意到当我添加两个正数时,总和溢出,我总是得到一个负数。
另一方面,如果我将两个负数相加,其和溢出,我总是得到一个正数(包括 0)。
我做了一些实验,但我想知道这是否适用于每种情况。
整数溢出是 C 中未定义的行为。
C 表示一个涉及整数的表达式溢出,如果它在通常的算术转换之后的结果是有符号类型的并且不能以结果的类型表示。赋值和强制转换表达式是一个例外,因为它们由整数转换决定。
无符号类型的表达式不能溢出,它们会换行,例如0U - 1
is UINT_MAX
。
例子:
INT_MAX + 1 // integer overflow
UINT_MAX + 1 // no overflow, the resulting type is unsigned
(unsigned char) INT_MAX // no overflow, integer conversion occurs
永远不要让任何整数表达式溢出,现代编译器(如gcc
)利用整数溢出作为未定义行为来执行各种类型的优化。
例如:
a - 10 < 20
whena
是int
提升后的类型,表达式在gcc
(启用优化时)减少为:
a < 30
它利用表达式是未定义的行为,当a
在范围内INT_MIN + 10 - 1
时INT_MIN
。
无法在a
is时进行此优化,unsigned int
因为如果a
is 0
,则a - 10
必须将其评估为UINT_MAX - 9
(没有未定义的行为)。优化a - 10 < 20
toa < 30
将导致与所需的结果不同的结果 when a
is 0
to 9
。
有符号整数的溢出在 C 中是未定义的行为,因此无法保证。
也就是说,环绕或算术模 2 N是类型中N
的位数,这是一种常见行为。对于这种行为,实际上如果总和溢出,则结果具有操作数的相反符号。
形式上,有符号算术溢出的行为是未定义的;任何事情都可能发生,而且它是“正确的”。这与完全定义溢出的无符号算术形成对比。
在实践中,许多较旧的编译器使用有符号算术,如您所描述的那样溢出。然而,现代 GCC 正在改变它的工作方式,你会非常不明智地依赖这种行为。当编译代码的环境中的任何内容发生变化时,它可能随时发生变化——编译器、平台、...
C中的溢出是一个可怕的混乱。
更糟糕的是这与整数提升的相互作用。多亏了提升,当看起来你正在做无符号算术时,你可以做有符号算术。例如考虑以下代码
uint16_t a = 65535;
uint16_t b = a * a;
在具有 16 位 int 的系统上,此代码是明确定义的。然而,在具有 32 位 int 的系统上,乘法将作为有符号 int 进行,由此产生的溢出将是未定义的行为!