提问人:Dan 提问时间:5/30/2021 最后编辑:Dan 更新时间:6/3/2021 访问量:731
是否定义了有符号整数溢出未定义的行为或实现?
Is signed integer overflow undefined behaviour or implementation defined?
问:
#include <limits.h>
int main(){
int a = UINT_MAX;
return 0;
}
我这个UB或实现定义了吗?
链接说它的UB
https://www.gnu.org/software/autoconf/manual/autoconf-2.63/html_node/Integer-Overflow-Basics
链接说其实现定义
转换规则 说:
否则,新类型将签名,并且无法在其中表示值;结果是实现定义的,或者是引发实现定义的信号。
我们不是把 a 转换成 a 吗?max unsigned value
signed value
在我看来,gcc 只是截断了结果。
答:
这两个参考文献都是正确的,但它们没有解决同一个问题。
int a = UINT_MAX;
不是有符号整数溢出的实例,此定义涉及从 到 的转换,其值超出了 type 的范围。正如巴黎综合理工学院网站所引述的那样,C 标准将行为定义为实现定义的。unsigned int
int
int
#include <limits.h>
int main(){
int a = UINT_MAX; // implementation defined behavior
int b = INT_MAX + 1; // undefined behavior
return 0;
}
以下是 C 标准的文本:
6.3.1.3 有符号和无符号整数
当具有整数类型的值转换为 以外的其他整数类型时,如果该值可以用新类型表示,则该值保持不变。
_Bool
否则,如果新类型是无符号的,则通过重复添加或减去比新类型中可以表示的最大值多 1 来转换该值,直到该值在新类型的范围内。
否则,新类型将签名,并且无法在其中表示值;结果是实现定义的,或者是引发实现定义的信号。
某些编译器具有命令行选项,用于将有符号算术溢出的行为从未定义的行为更改为实现定义的行为:,并支持强制整数计算以 232 或 264 为模执行,具体取决于有符号类型。这既可以防止一些有用的优化,也可以防止一些违反直觉的优化,这些优化可能会破坏看起来无辜的代码。有关一些示例,请参阅此问题:-fwrapv 有什么作用?gcc
clang
-fwrapv
评论
if (i+1 < i)
if (0)
这在无符号整数溢出中:
int a = UINT_MAX;
它是从无符号到有符号整数类型的转换,并且是实现定义的。C 标准的第 6.3.1.3 节中介绍了有符号和无符号整数类型的转换:
1 当一个整数类型的值被转换为另一个整数类型时,如果该值可以用新的 类型,它是不变的。
_Bool
2 否则,如果新类型是无符号的,则通过重复加或减 1 来转换该值 可以在新类型中表示的最大值 直到该值在新类型的范围内6。
3 否则,新类型是有符号的,并且不能在其中表示值;结果是实现定义的 或者引发实现定义的信号。
有符号整数溢出的一个示例如下:
int x = INT_MAX;
x = x + 1;
这是不确定的。事实上,C 标准的第 3.4.3 节在第 4 段中定义了未定义的行为状态:
未定义行为的一个示例是整数溢出时的行为
整数溢出仅适用于 6.2.5p9 中的有符号类型:
有符号整数类型的非负值范围是对应的无符号整数类型的子范围,并且每种类型中相同值的表示形式相同。涉及无符号操作数的计算永远不会溢出,因为无法由生成的无符号整数类型表示的结果被缩减为比结果类型可以表示的最大值大 1 的数字
int a = UINT_MAX;
不会溢出,因为在计算此声明或其中的表达式时不会发生异常情况。此代码被定义为转换为初始化的类型,转换由 C 2018 6.3.1.3 中的规则定义。UINT_MAX
int
a
简而言之,适用的规则是:
- 6.7.9 11 表示初始化的行为类似于简单赋值:“......对象的初始值是表达式的初始值(转换后);与简单赋值相同的类型约束和转换适用,...”
- 6.5.16.1 2 表示简单赋值执行转换:“在简单赋值 () 中,右操作数的值被转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。
=
- 6.3.1.3 3 涵盖了当操作数值无法在类型中表示时转换为有符号整数类型,它说:“要么结果是实现定义的,要么引发实现定义的信号。
因此,定义了行为。
2018 6.5 5 中有一个关于计算表达式时发生的异常情况的一般规则:
如果在计算表达式期间发生异常情况(即,如果结果未以数学方式定义或不在其类型的可表示值范围内),则该行为未定义。
但是,此规则从不适用于上述链条。在进行评估时,包括初始化的隐含赋值,我们永远不会得到超出其类型范围的结果。转换的输入超出了目标类型的范围,但转换的结果在范围内,因此没有超出范围的结果来触发异常情况。int
(一个可能的例外是,我想 C 实现可以将转换结果定义为超出 的范围。我不知道有没有这样做,这可能不是 6.3.1.3 3 的意图。int
在预先存在的“语言”(方言家族)中,C标准被编写来描述,实现通常要么通过执行底层平台所做的任何事情来处理有符号整数溢出,要么将值截断到底层类型的长度(这是大多数平台所做的),即使在其他会做其他事情的平台上也是如此,或者触发某种形式的信号或诊断。 在K&R的《C编程语言》一书中,这种行为被描述为“依赖于机器”。
尽管该标准的作者在已发布的 Rationale 文档中表示,他们期望普通平台的实现会以普通的方式运行,但他们不想说某些操作会定义某些平台上的行为,而不会定义其他平台上的行为。此外,将行为描述为“实现定义”会产生问题。考虑这样的事情:
int f1(void);
int f2(int a, int b, int c);
int test(int x, int y)
{
int test = x*y;
if (f1())
f2(test, x, y);
}
如果整数溢出的行为是“实现定义”,那么任何可能引发信号或具有其他可观察到的副作用的实现都需要在调用 f1() 之前执行乘法,即使乘法的结果将被忽略,除非 f1() 返回非零值。将其归类为“未定义的行为”可以避免此类问题。
不幸的是,gcc 将这种分类解释为“未定义的行为”,作为以不受普通因果律约束的方式处理整数溢出的邀请。给定一个函数,如下所示:
unsigned mul_mod_32768(unsigned short x, unsigned short y)
{
return (x*y) & 0x7FFFu;
}
尝试以大于 可能任意破坏周围代码的行为,即使函数的结果不会以任何可观察的方式使用。x
INT_MAX/y
评论
UINT_MAX
int
999999999*999999999
999999999*999999999
999999999*999999999
int
long long int a = 999999999*999999999;
*