如何在 GCC 中收到无符号整数溢出警告而不是环绕警告?

Und*_*ior 6 c gcc gcc-warning

测试环境

  • Linux
  • 英特尔 x86-64 GCC 8.2.1
  • 启用标志:-Wextra -Wall -Wfloat-equal -Wundef -Wshadow -Winit-self -Wpointer-arith -Wcast-align -Wstrict-prototypes -Wstrict-overflow=5 -Wwrite-strings -Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wconversion -Wunreachable-code -Wformat=2 -pedantic -pedantic-errors -Werror-implicit-function-declaration -Wformat-security -fstrict-overflow
  • sizeof(long)是 8。
  • sizeof(int)是 4。

示例1,收到警告,很好:

long x = 2147483647 * 3;
Run Code Online (Sandbox Code Playgroud)

示例2,没有警告,不好:

long x = 2147483647U * 3U; // Suffix U
Run Code Online (Sandbox Code Playgroud)

或者

unsigned int a = 2147483647;
unsigned int b = 3;
long x = a*b;
Run Code Online (Sandbox Code Playgroud)

示例 3,没有警告,但按预期工作:

long x = 2147483647L * 3L; // Suffix L
Run Code Online (Sandbox Code Playgroud)

在示例2中,我知道这是一个环绕而不是整数溢出,但这些是编译器无法警告的情况?

从标准来看:

(6.3.1.8)

否则,将对两个操作数执行整数提升。然后将以下规则应用于提升的操作数:

如果两个操作数具有相同的类型,则不需要进一步转换。否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较大等级的操作数的类型。

否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数的类型的等级,则有符号整数类型的操作数将转换为无符号整数类型的操作数的类型。

否则,如果有符号整数类型操作数的类型可以表示无符号整数类型操作数类型的所有值,则将无符号整数类型操作数转换为有符号整数类型操作数的类型。

否则,两个操作数都转换为与有符号整数类型操作数的类型相对应的无符号整数类型。

(6.5):

如果在计算表达式期间出现异常情况(即,如果结果未在数学上定义或不在其(类型)的可表示值范围内),则行为未定义。


开始使用带有标志的 Clang,-fsanitize=unsigned-integer-overflow这对处理环绕中不需要的值有很大帮助。这不是整数溢出,但不是预期的值。自 GCC 以来,到目前为止还不支持这样的警告,请继续使用 Clang。

dbu*_*ush 4

有符号整数溢出会引发未定义的行为,而无符号整数溢出是明确定义的。

对于无符号整数,会发生溢出,就像对给定类型的最大值进行模一计算一样。换句话说,如果类型是位宽的,则仅保留结果的低位n。n这实际上并不是溢出,而是称为环绕。

第 6.5p9 节对此进行了详细说明:

有符号整数类型的非负值的范围
是相应无符号整数类型的子范围,并且每个类型中相同值的表示是相同的。 涉及无符号操作数的计算永远不会溢出,因为无法由结果无符号整数类型表示的结果会按比结果类型可以表示的最大值大 1 的数字进行减模。

由于此行为已明确定义,因此编译器触发警告是没有意义的。

对于你的第二个例子:

long x = 2147483647U * 3U; 
Run Code Online (Sandbox Code Playgroud)

乘法是在unsigned类型上完成的,因此数学结果 6442450941 会回绕到 2147483645,它在 a 的范围内long。没有溢出(只是环绕),也没有超出范围的转换,因此没有警告。