【问题标题】:Is signed integer overflow undefined behaviour or implementation defined?有符号整数溢出未定义行为或实现是否已定义?
【发布时间】:2021-06-03 01:44:58
【问题描述】:
#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

转换规则说:

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

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

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

【问题讨论】:

  • 你必须保持直接的conversionoverflow in expression。这里溢出发生在 converting UINT_MAX 为分配类型int。那是IDB。当在实际计算表达式时发生溢出时,如999999999*999999999,那就是UB。
  • 我不明白吗?在这里,您还将 999999999*999999999 的结果转换为 int 类型,这实际上会截断它。
  • C 不是这样定义的。执行999999999*999999999 未定义为“进行数学运算,然后根据通常的转换规则执行转换”。它被定义为“在概念上进行数学运算,但如果结果不能以int 类型表示,则 UB”。转换规则永远不会发挥作用。
  • 如果有助于理解,long long int a = 999999999*999999999; 也是 UB。只要评估了* 运算符,UB 就会发生。您尝试对结果执行的操作无关紧要。

标签: c undefined-behavior implementation-defined-behavior


【解决方案1】:

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

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

  1. 当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则不变。

  2. 否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。

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

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

【讨论】:

  • 为什么有符号整数溢出是未定义的行为(所以if (i+1 &lt; i)可以优化为if (0)然后删除)而不是实现定义的行为(所以行为是根据硬件)?
  • @pmor: 有符号整数溢出 被指定为具有未定义的行为,因为标准 C 委员会如此决定。优化编译器编写器利用这一点来实现各种优化
  • 您知道为什么 C 委员会会这样决定吗?可能是为了让编译器更好地优化从而带来更高的性能。
【解决方案2】:

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.)

【讨论】:

    【解决方案3】:

    这在无符号整数溢出中:

    int a = UINT_MAX; 
    

    它是从无符号到有符号整数类型的转换,并且是实现定义的。这在C standard 的第 6.3.1.3 节中涉及到有符号和无符号整数类型的转换:

    1 当一个整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新的表示 类型,它是不变的。

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

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

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

    int x = INT_MAX;
    x = x + 1;
    

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

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

    并且整数溢出仅适用于 6.2.5p9 中的带符号类型:

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

    【讨论】:

      【解决方案4】:

      在预先存在的“语言”(方言家族)中,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;
      }
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-23
        • 2017-05-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多