3
#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

转换规则说:

否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

我们不是将 a 转换max unsigned value为 asigned value吗?

我所看到的方式,gcc 只是截断了结果。

4

4 回答 4

8

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

int a = UINT_MAX;不是有符号整数溢出的实例,此定义涉及从unsigned int到的转换,int其值超出 type 的范围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强制执行以 2 32或 2 64为模的整数计算。这阻止了一些有用的优化,但也阻止了一些可能破坏无辜代码的违反直觉的优化。有关示例,请参阅此问题:-fwrapv 做什么?

于 2021-05-29T20:11:37.577 回答
1

这在无符号整数溢出中:

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 中的有符号类型:

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

于 2021-05-29T20:28:05.093 回答
1

int a = UINT_MAX;不会溢出,因为在评估此声明或其中的表达式时没有发生异常情况。此代码定义为转换为初始化UINT_MAX的类型,转换由C 2018 6.3.1.3中的规则定义。inta

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

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

因此,行为已定义。

2018 6.5 5 中有一条关于评估表达式时发生的异常情况的一般规则:

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

然而,这条规则永远不会应用在上面的链中。在进行评估时,包括初始化的隐含赋值,我们永远不会得到超出其类型范围的结果。转换的输入超出了目标类型的范围int,但是转换的结果在范围内,所以没有超出范围的结果触发异常情况。

(一个可能的例外是,我认为 C 实现可以将转换的结果定义为超出范围int。我不知道有什么这样做的,这可能不是 6.3.1.3 的意图3.)

于 2021-05-29T21:18:50.857 回答
0

在预先存在的“语言”(方言家族)中,编写 C 标准来描述,实现通常会通过执行底层平台所做的任何事情来处理有符号整数溢出,将值截断为底层类型的长度(这就是大多数平台都这样做了),即使是在本来会做其他事情或触发某种形式的信号或诊断的平台上也是如此。在 K&R 的“The C Programming Language”一书中,该行为被描述为“机器相关”。

尽管该标准的作者在已发布的基本原理文档中指出了一些他们期望普通平台的实现会以普通方式运行的情况,但他们不想说某些操作会在某些平台上定义行为但在其他平台上没有定义. 此外,将行为表征为“实现定义的”会产生问题。考虑类似的事情:

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可能会任意破坏周围代码的行为,即使函数的结果不会以任何可观察的方式使用。

于 2021-06-02T17:44:58.413 回答