c-是否定义了有符号整数溢出的未定义行为或实现


#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

允许C/C++中的有符号整数溢出

说明其实现定义的链接

http://www.enseignement.polytechnique.fr/informatique/INF478/docs/Cpp/en/c/language/signed_and_unsigned_integers.html

转换规则说:

否则,将对新类型进行签名,并且无法在其中表示值;要么结果是实现定义的,要么产生实现定义的信号。

我们不是在把max unsigned value转换成signed value吗?

在我看来,gcc只是截断了结果。

两个引用都是正确的,但它们没有解决相同的问题。

int a = UINT_MAX;不是带符号整数溢出的实例,此定义涉及从unsigned int到值超过类型int范围的int的转换。正如École polytechnique网站上引用的那样,C标准将行为定义为实现定义。

#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有符号和无符号整数

  1. 将具有整数类型的值转换为_Bool以外的另一个整数类型时,如果该值可以用新类型表示,则该值不变。

  2. 否则,如果新类型是无符号的,则通过重复地将新类型中可以表示的最大值多加或减去一来转换该值,直到该值在新类型的范围内为止。

  3. 否则,将对新类型进行签名,并且无法在其中表示值;要么结果是实现定义的,要么产生实现定义的信号。

一些编译器有一个命令行选项,可以将有符号算术溢出的行为从未定义的行为更改为已定义的实现:gccclang支持-fwrapv,根据有符号类型,强制执行以232或264为模的整数计算。这防止了一些有用的优化,但也防止了一些可能破坏无辜代码的违反直觉的优化。请参阅以下问题以获取一些示例:-fwrapv做什么?

int a = UINT_MAX;不会溢出,因为在评估此声明或其中的表达式时没有出现异常条件。此代码是定义的,用于将UINT_MAX转换为类型int以初始化a,转换由C 2018 6.3.1.3中的规则定义。

简而言之,适用的规则是:

  • 6.7.9 11表示初始化的行为类似于简单赋值:"…对象的初始值是表达式的初始值(转换后);应用与简单赋值相同的类型约束和转换,…">
  • 6.5.16.1 2表示简单赋值执行转换:"在简单赋值(=)中,右操作数的值被转换为赋值表达式的类型,并替换左操作数指定的对象中存储的值。">
  • 6.3.1.3.3涵盖了当操作数值无法在类型中表示时转换为带符号整数类型的问题,它说:"要么结果是实现定义的,要么引发了实现定义的信号。">

因此,行为已定义。

2018 6.5 5中有一条关于评估表达式时出现的异常条件的一般规则:

如果在表达式求值过程中出现异常条件(即,如果结果未在数学上定义或不在其类型的可表示值范围内),则行为未定义。

但是,此规则从不适用于上面的链。在进行评估时,包括初始化的隐含赋值,我们永远不会得到超出其类型范围的结果。转换的输入超出目标类型int的范围,但转换的结果在范围内,因此没有超出范围的结果触发异常条件。

(一个可能的例外是,我想,C实现可以将转换结果定义为超出int的范围。我不知道有任何这样做,这可能不是6.3.1.3.3的意图。)

这是无符号整数溢出:

int a = UINT_MAX; 

它是从无符号整数类型到有符号整数类型的转换,是实现定义的。关于有符号和无符号整数类型的转换,C标准第6.3.1.3节对此进行了说明:

1当具有整数类型的值转换为_Bool以外的另一个整数类型时,如果该值可以用类型,它是不变的。

2否则,如果新类型是无符号的,则通过重复加或减一来转换值可以在新类型中表示的最大值直到该值在新类型的范围内。6

3否则,将对新类型进行签名,并且无法在其中表示值;结果要么是实现定义的或者产生实现定义的信号

有符号整数溢出的一个例子是:

int x = INT_MAX;
x = x + 1;

这个是未定义的。事实上,C标准第3.4.3节定义了第4段中未定义的行为状态:

未定义行为的一个例子是整数溢出上的行为

根据6.2.5p9:,整数溢出仅适用于有符号类型

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

在预先存在的"语言;(方言家族)编写C标准是为了描述,实现通常要么通过执行底层平台所做的任何操作来处理有符号整数溢出,要么将值截断为底层类型的长度(这是大多数平台所做做的),即使在原本会执行其他操作的平台上也是如此,要么触发某种形式的信号或诊断。在K&R的书";"C程序设计语言";,行为被描述为";机器相关";。

尽管该标准的作者在已发布的基本原理文件中指出,在某些情况下,他们预计普通平台的实现将以普通的方式进行,但他们不想说某些操作将定义某些平台上的行为,而不是其他平台上的行为。此外,将该行为表征为";实现定义的";会造成问题。比如:

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);
}

如果整数溢出的行为是"0";实现定义";,那么,任何可能引发信号或具有其他可观察到的副作用的实现都需要在调用f1()之前执行乘法,即使乘法的结果将被忽略,除非f1()返回非零值。将其分类为";未定义的行为;避免此类问题。

不幸的是,gcc将该分类解释为";未定义的行为;作为一种邀请,以不受普通因果律约束的方式处理整数溢出。给定一个类似的函数

unsigned mul_mod_32768(unsigned short x, unsigned short y)
{
return (x*y) & 0x7FFFu;
}

尝试用大于INT_MAX/yx调用它可能会任意破坏周围代码的行为,即使函数的结果本来不会以任何可观察的方式使用。

相关内容

  • 没有找到相关文章

最新更新