C 为什么整数除以-1(负1)会导致FPE?

C 为什么整数除以-1(负1)会导致FPE?,c,gcc,x86,arm64,floating-point-exceptions,C,Gcc,X86,Arm64,Floating Point Exceptions,我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以很容易地完成其他的事情,但这件事让我很困惑 代码段1输出-2147483648 int a = 0x80000000; int b = a / -1; printf("%d\n", b); 代码片段2不输出任何内容,并给出一个浮点异常 int a = 0x80000000; int b = -1; int c = a / b; printf("%d\n", c); 我很清楚代码片段1产生

我的任务是解释一些看似奇怪的C代码行为(在x86上运行)。我可以很容易地完成其他的事情,但这件事让我很困惑

代码段1输出-2147483648

int a = 0x80000000;
int b = a / -1;
printf("%d\n", b);

代码片段2不输出任何内容,并给出一个浮点异常

int a = 0x80000000;
int b = -1;
int c = a / b;
printf("%d\n", c);
我很清楚代码片段1产生结果的原因(1+~INT\u MIN==INT\u MIN),但我不太明白整数除以-1如何生成FPE,也不能在我的Android手机上重现它(AArch64,GCC 7.2.0)。代码2的输出与代码1相同,没有任何异常。这是x86处理器的一个隐藏bug特性吗

这项作业没有说明任何其他内容(包括CPU体系结构),但由于整个课程都基于桌面Linux发行版,您可以放心地假设它是一款现代的x86


编辑:我联系了我的朋友,他在Ubuntu 16.04(英特尔卡比湖,GCC 6.3.0)上测试了代码。结果与指定的内容一致(代码1输出所述内容,代码2使用FPE崩溃)。

非常可能发生的事情,有时确实会发生

你的问题在C语言中没有意义。但是您可以获得汇编代码(例如,由
gcc-O-fverbose asm-S
生成)并关心机器代码的行为

在x86-64上,Linux整数溢出(以及整数除零,IIRC)会给出一个
SIGFPE
信号。看

顺便说一句,在PowerPC上,据说整数除以零在机器级别得到-1(但是一些C编译器生成额外的代码来测试这种情况)

问题中的代码在C中是未定义的行为。生成的汇编程序代码有一些已定义的行为(取决于和处理器)

(完成作业是为了让你阅读更多关于UB的内容,尤其是你绝对应该阅读的内容)

很多事情都可能发生,有时确实会发生

你的问题在C语言中没有意义。但是您可以获得汇编代码(例如,由
gcc-O-fverbose asm-S
生成)并关心机器代码的行为

在x86-64上,Linux整数溢出(以及整数除零,IIRC)会给出一个
SIGFPE
信号。看

顺便说一句,在PowerPC上,据说整数除以零在机器级别得到-1(但是一些C编译器生成额外的代码来测试这种情况)

问题中的代码在C中是未定义的行为。生成的汇编程序代码有一些已定义的行为(取决于和处理器)


(完成作业是为了让您阅读更多关于UB的内容,尤其是您应该绝对阅读的内容)

有符号的
int
如果出现以下情况,则两个补码中的除法是未定义的:

  • 除数为零,或
  • 被除数为
    INT\u MIN
    (=
    0x8000000
    如果
    INT
    int32\u t
    ),除数为
    -1
    (以二补形式,
    -INT\u MIN>INT\u MAX
    ,这会导致整数溢出,这是C中未定义的行为)
  • (建议在检查此类边缘情况的函数中包装整数操作)


    由于您正在通过违反规则2来调用未定义的行为,因此任何事情都可能发生,并且在发生时,您平台上的这一特定事情恰好是由处理器生成的FPE信号。

    Signed
    int
    如果出现以下情况,则二的除法是未定义的:

  • 除数为零,或
  • 被除数为
    INT\u MIN
    (=
    0x8000000
    如果
    INT
    int32\u t
    ),除数为
    -1
    (以二补形式,
    -INT\u MIN>INT\u MAX
    ,这会导致整数溢出,这是C中未定义的行为)
  • (建议在检查此类边缘情况的函数中包装整数操作)

    由于您正在通过违反规则2来调用未定义的行为,因此任何事情都可能发生,并且在发生时,平台上的这一特定事件恰好是处理器生成的FPE信号。

    如果您通过实际使用操作进行除法,则在x86上(这对于常量参数来说并不是必需的,即使对于已知为常量的变量也是如此,但无论如何都会发生),
    INT_MIN/-1
    是导致#DE(除法错误)的情况之一这确实是商超出范围的一种特殊情况,一般来说这是可能的,因为
    idiv
    将额外的宽除数除以除数,如此多的组合会导致溢出-但是
    INT_MIN/-1
    是唯一一种不是div-by-0的情况,通常可以从更高级别的语言访问,因为它们调用y不公开超宽红利功能

    Linux令人烦恼地将#DE映射到SIGFPE,这可能让所有第一次处理它的人都感到困惑。

    在x86上,如果实际使用该操作进行除法(这对于常量参数来说并不是必需的,即使对于已知为常量的变量也不是,但无论如何都会发生),
    INT_MIN/-1
    是导致#DE(除法错误)的情况之一这确实是商超出范围的一种特殊情况,一般来说这是可能的,因为
    idiv
    将额外的宽除数除以除数,如此多的组合会导致溢出-但是
    INT_MIN/-1
    是唯一一种不是div-by-0的情况,通常可以从更高级别的语言访问,因为它们调用y不公开超宽红利功能


    Linux令人恼火地将#DE映射到SIGFPE,这可能让所有第一次处理它的人都感到困惑。

    这里有四件事:

    • gcc-O0
      行为解释了您的两个版本之间的差异:
      idiv
      neg
      (而
      clang-O0
      恰好使用
      idiv
      编译它们)。以及为什么即使使用compile ti也会出现这种情况
      int a = 0x80000000;
      int b = -1;
        // debugger can stop here on a breakpoint and modify b.
      int c = a / b;        // a and b have to be treated as runtime variables, not constants.
      printf("%d\n", c);
      
          # int c = a / b  from x86_fault()
          mov     eax, DWORD PTR [rbp-4]
          cdq                                 # dividend sign-extended into edx:eax
          idiv    DWORD PTR [rbp-8]           # divisor from memory
          mov     DWORD PTR [rbp-12], eax     # store quotient
      
          # int c = a / b  from x86_fault()  (which doesn't fault on AArch64)
          ldr     w1, [sp, 12]
          ldr     w0, [sp, 8]          # 32-bit loads into 32-bit registers
          sdiv    w0, w1, w0           # 32 / 32 => 32 bit signed division
          str     w0, [sp, 4]
      
      int x86_fault() {
          int a = 0x80000000;
          int b = -1;
          int c = a / b;
          return c;
      }
      
      x86_fault:
          mov     eax, -2147483648
          ret
      
      x86_fault:
          ret
      
      int *local_address(int a) {
          return &a;
      }
      
      local_address:
          xor     eax, eax     # return 0
          ret
      
      void foo() {
          int *p = local_address(4);
          *p = 2;
      }
      
       foo:
         mov     DWORD PTR ds:0, 0     # store immediate 0 into absolute address 0
         ud2                           # illegal instruction
      
      1000_0000_0000_0000_0000_0000_0000
      
      0111_1111_1111_1111_1111_1111_1111 + 1 =>
      1000_0000_0000_0000_0000_0000_0000  !!!  the same original number.
      
      1000_0000_0000_0000_0000_0000_0000 &
      0111_1111_1111_1111_1111_1111_1111 =>
      0000_0000_0000_0000_0000_0000_0000