【问题标题】:Integer overflow in intermediate arithmetic expression中间算术表达式中的整数溢出
【发布时间】:2018-05-05 05:37:44
【问题描述】:

这可能是一个非常基本的编程问题,但我一直想了解它。

考虑这个简单的例子:

int main(void) 
{
  unsigned char a = 5;
  unsigned char b = 20;
  unsigned char m = 0xFF;

  unsigned char s1 = m + a - b;
  unsigned char s2 = m - b + a;
  printf("s1 %d s2 %d", s1, s2);
  return 0;
}

鉴于在 C 中算术运算符是从左到右计算的,这里的第一个计算应该在 m + a 处溢出。但是,运行此程序会为 s1 和 s2 返回相同的答案。 我的问题是:第一个表达式是否会因为溢出而导致未定义的行为? 第二个表达式应该避免溢出,但我想了解为什么两个表达式返回相同的答案。

【问题讨论】:

  • 不幸的是,这个问题的格式不正确。在进行算术运算之前,C 将整数操作数至少提升为 int,因此问题中的代码没有溢出。如果使用确实溢出的代码并得到相同的结果,答案将是编译器经常使用有效回绕的算术指令,因此即使发生中间溢出也可以获得数学上正确的答案。但是,不能依赖这种行为,因为它不受 C 标准的保证,并且各种事情都可能导致其他结果。

标签: c undefined-behavior integer-overflow


【解决方案1】:

由于 C 的整数提升,s1 计算有效地执行为:

unsigned char s1 = (unsigned char)( (int)m + (int)a - (int)b );

并且没有临时溢出。

【讨论】:

    【解决方案2】:

    (已更正)在对整数类型进行算术运算时,所有小于 int 的类型在计算过程中都被提升为 int,如果结果类型更小,则将其截断。

    见:

    https://wiki.sei.cmu.edu/confluence/display/c/INT02-C.+Understand+integer+conversion+rules

    【讨论】:

    • 啊——你确实是对的——我已经确定了答案。
    • 在这种情况下,如果 a、b 和 m 都是整数(或长整数)并且 m + a 溢出会发生什么?
    • @npn intlong 已签名,在这种情况下溢出是未定义的行为。但是,如果您的意思是 unsigned intunsigned long 溢出,那么它们也是明确定义的。
    【解决方案3】:

    根据ISO C specification §6.2.5.9

    涉及无符号操作数的计算永远不会溢出, 因为无法由生成的无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。

    这意味着在您的加法和减法中似乎分别发生的可能是正的 和负的 溢出实际上都是作为有符号的int 执行的,因此它们都是明确定义的。在计算表达式后,结果会被截断回 unsigned char,因为这是左侧的结果类型。

    【讨论】:

      【解决方案4】:

      对小于int 的类型的操作是通过将结果转换为int,进行计算,然后将结果转换回原始类型来执行的。对于小的无符号类型,如果计算结果适合类型int,这将导致结果的高位被静默忽略。该标准的已发布理由表明,作者预计,在将值存储到不大于 int 的无符号类型中时,非古老实现将忽略高位,而不考虑计算是否适合 @987654325 类型@,但“现代”编译器以这种方式可靠运行已不再流行。以 16 位 short 和 32 位 int 为例,函数

      unsigned mulMod65536(unsigned short x, unsigned short y)
      { return (x*y) & 0xFFFFu; }
      

      通常的行为方式相当于:

      unsigned mulMod65536(unsigned short x, unsigned short y)
      { return (1u*x*y) & 0xFFFFu; }
      

      但在某些情况下,gcc 会根据以下事实做出“聪明”的优化: 如果 x*y 超过 2147483647,它可以以任意方式运行,即使高位没有理由影响结果。

      涉及小有符号类型的操作与使用无符号类型的操作类似,不同之处在于允许实现将超出较小类型范围的值以实现定义的方式映射到这些类型的值,或者在以下情况下引发实现定义的信号尝试转换超出范围的值。实际上,即使在这种情况下,几乎所有实现都使用补码截断。虽然在某些情况下其他一些行为可能更便宜,但该标准要求实现以一致的记录方式运行。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-07-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-07
        • 2021-02-14
        • 1970-01-01
        相关资源
        最近更新 更多