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

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

Allowing signed integer overflows in C/C++

链接说明其实现已定义

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

转换规则说:

Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

据我所知,gcc 只是截断了结果。

两个参考文献都正确,但它们没有解决同一个问题。

int a = UINT_MAX;不是有符号整数溢出的实例,这个定义涉及从unsigned intint的转换,其值超出类型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 Signed and unsigned integers

  1. When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

  2. Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

  3. Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

无符号整数溢出:

int a = UINT_MAX; 

它是从无符号整数类型到有符号整数类型的转换,并且是实现定义的C standard 第 6.3.1.3 节介绍了有关带符号和无符号整数类型转换的内容:

1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.6

3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

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

int x = INT_MAX;
x = x + 1;

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

An example of undefined behavior is the behavior on integer overflow

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

The range of nonnegative values of a signed integer type is a subrange of the corresponding unsigned integer type, and the representation of the same value in each type is the same. A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type

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

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

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

因此,行为已定义。

2018 6.5 5 中关于计算表达式时发生的异常情况的一般规则:

If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.

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

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

的意图

在预先存在的“语言”(方言家族)中,编写 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() returns 为非零值,否则乘法将被忽略。将其归类为“未定义行为”可避免此类问题。

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

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

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