【问题标题】:Does INT_MIN % -1 produce undefined behavior?INT_MIN % -1 会产生未定义的行为吗?
【发布时间】:2011-05-08 01:19:03
【问题描述】:

gcc 生成浮动代码,为以下代码引发 SIGFPE

#include <limits.h>
int x = -1;
int main()
{
    return INT_MIN % x;
}

但是,我在标准中找不到任何声明该代码调用未定义或实现定义的行为。据我所知,它需要返回 0。这是 gcc 中的错误还是我错过了标准产生的一些特殊异常?

【问题讨论】:

  • 注意全局变量是为了防止gcc优化计算。
  • 你期望得到一个负数的模的结果是什么?
  • “当 a 或 n 为负时,这种幼稚的定义就会失效,编程语言在定义这些值的方式上也会有所不同”。 en.wikipedia.org/wiki/Modulo_operation
  • 我希望INT_MIN 的余数除以-1,在这种情况下,无论您将余数定义为正数还是负数,它都为零,因为-1 可以除以任何东西均匀。
  • C 在这种情况下定义了它们,并且在任何情况下,当分母为 1 或 -1 时,定义没有歧义,因为余数本质上为零。

标签: c gcc overflow division modulo


【解决方案1】:

您可能是对的,这可以被视为实际标准中的错误。 current draft 解决了这个问题:

如果商 a/b 是可表示的, 表达式 (a/b)*b + a%b 应 等于a;否则,行为 a/b 和 a%b 都是未定义的。

【讨论】:

  • 接受;这个答案解决了委员会通过的错误和修复。抱歉,拖了这么久。
  • 这种语言是否可能包含在 TC 到 C99 中?
  • @caf,我猜C99不会有TC,因为C1x打算取代它。
【解决方案2】:

查看gcc生成的汇编代码(x在汇编前面定义为-1):

movl    x, %ecx
movl    $-2147483648, %eax
movl    %eax, %edx
sarl    $31, %edx
idivl   %ecx

第一条计算指令sarl-2147483648右移31位。这导致-1 被放入%edx

接下来执行idivl。这是一个签名操作。让我引用描述:

将组合的 %edx:%eax 寄存器中包含的双字的内容除以指定的寄存器或内存位置中的值。

所以-1:-2147483648 / -1 是发生的分裂。 -1:-2147483648 解释为双字等于 -2147483648(在二进制补码机器上)。现在-2147483648 / -1 发生,返回2147483648。繁荣!这比INT_MAX多了一个。


关于为什么的问题,这是 gcc 中的错误还是我错过了标准造成的一些特殊异常?

在 C99 标准中,这是隐式 UB(第 6.5.5/6 节):

... / 运算符的结果是任何小数部分被丢弃的代数商。88) 如果商 a/b 是可表示的,则表达式 (a/b)*b + a%b 应等于 a。

INT_MIN / -1 无法表示,因此这是 UB。

然而,在 C89 中,% 运算符是实现定义的,这是否是编译器错误可以争论。但是,该问题在 gcc 中列出:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30484

【讨论】:

  • 您关于如何SIGFPE 发生的信息是正确的。不过,我担心的是这是否正确。我从来没有要求编译器计算INT_MIN/-1(这显然会导致UB,因为它溢出),只是为了计算INT_MIN%-1,我在标准中找不到后者调用UB的语句。仍然是一个信息丰富的答案。
  • 关于您对 6.5.5/6 的引用:在上一段 (5) 中,它显示为“% 运算符的结果是余数”。第 6 段阐明了当“余数”可能是模棱两可时,但当ab 的整数倍时,“余数”不模棱两可;在这种情况下,唯一的余数是 0。
  • @R.: 嗯,等一下,标准规定“如果商 a/b 是可表示的”,而 INT_MIN % -1 中的情况并非如此。它没有说明发生这种情况时应该发生什么,因此它是 UB。
  • 第5段已经说明%的结果是“余数”。如所写,第 6 段仅阐明了它所谈论的“其余部分”。我认为您可能是对的,intent 不是为 INT_MAX%-1 定义任何行为,但我不知道当前语言将如何实现这一点,因为它已经在上面定义了它......
  • 标准中说“如果商 a/b 在结果的类型中是可表示的,则 (a/b)*b + a%b 等于 a。”此语句不限制在商不可可表示的情况下的行为,这意味着前面的语句仍然有效。前面的语句指出% 产生余数。在这种情况下,余数是 0。
【解决方案3】:

【讨论】:

  • 感谢您提供的信息链接。至少看起来人们大多同意这是一个错误,但是“默认情况下让它被破坏并需要一个特殊的选项来修复它”的方法听起来很荒谬......
  • 如果x!=-11 如果x==1,是否有任何简单的按位/算术表达式计算为x?如果是这样,当/ 不使用相同的分母执行时,编译器可以在使用变量分母执行% 之前将其应用于分母。
  • @R..: “默认关闭,但在符合标准的模式下打开”听起来不错,这就是该错误的建议。 gcc 不是一个符合标准的实现,也没有声称是。 gcc -std=c99 也不完全符合,但它确实试图……
【解决方案4】:

此处提出的问题与缺陷报告相同

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#614

不幸的是,我没有看到它在决议部分明确说明它应该产生 UB。除法确实会产生 UB,但对于 % 运算符来说并不明显。

【讨论】:

    【解决方案5】:

    具有负操作数的取模运算的结果在 C89 中由实现定义,并在 C99 中由 §6.5.5/6 定义:

    .../ 运算符的结果是任何小数部分被丢弃的代数商。88) 如果商 a/b 是可表示的,则表达式 (a/b)*b + a%b 应等于a

    88) 这通常被称为“向零截断”。

    对于二进制补码表示,INT_MIN / -1 等于 INT_MAX + 1,因此它不能表示为不带包装的 int,我猜该实现选择使其具有爆炸性。

    【讨论】:

    • / 运算符没有出现在我的程序中,但是。无论您如何使用负操作数定义结果(“舍入”为 0 或向下),-1 均分任何内容,因此数学上唯一可能的余数为零。
    • @R..:我只是引用了关于负操作数的行为的参考。由于 GCC 实现了计算失败,所以它基本上是一个错误,如果你需要我用这么多的话说出来。
    • @R..: 换一种说法,% 仅在定义了/ 时才定义,并且由于这里/ 未定义,% 也未定义,即使它可能不应该。
    猜你喜欢
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    • 2016-09-15
    • 2011-08-25
    • 2013-04-24
    • 2013-05-28
    • 2019-08-04
    • 1970-01-01
    相关资源
    最近更新 更多