Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/68.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 为什么整数除以-1(负1)会导致FPE?_C_Gcc_X86_Arm64_Floating Point Exceptions - Fatal编程技术网

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的内容,尤其是您应该绝对阅读的内容)

有符号的
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,这可能让所有第一次处理它的人都感到困惑。

    这里有四件事:

    • gcc-O0
      行为解释了这两个版本之间的差异:
      idiv
      neg
      (而
      clang-O0
      恰好使用
      idiv
      编译它们)。以及为什么即使使用编译时间常量操作数也会得到这种结果

    • x86故障行为与ARM上除法指令的行为

    • 如果整数运算导致信号被传递,POSIX要求它是SIGFPE:但POSIX不要求任何特定整数运算的陷波(这就是为什么允许x86和ARM不同)

      单一的Unix规范称为“错误的算术运算”。它以浮点命名,令人困惑,但在FPU处于默认状态的正常系统中,只有整数数学将引发它。在x86上,只有整数除法。在MIPS上,编译器可用于有符号数学,因此可以在有符号添加溢出时获取陷阱。(,但未定义的行为检测器可能使用
      add

    • C未定义的行为规则(有符号的溢出,特别是除法),让gcc发出代码,在这种情况下,代码可能会被捕获


    没有选项的gcc与gcc-O0相同

    -O0
    减少编译时间并使调试产生预期的结果。这是默认设置

    这解释了两个版本之间的差异:

    gcc-O0
    不仅不尝试优化,它还积极地进行反优化,使asm能够独立地实现函数中的每个C语句。这样可以安全地工作,让您跳转到函数中的另一行,就像您在C源代码中真的跳转一样。详细解释了
    -O0以它的方式编译

    它也不能对语句之间的变量值做任何假设,因为您可以使用
    设置b=4
    来更改变量。这显然对性能有灾难性的影响,这就是为什么
    -O0
    代码的运行速度比普通代码慢几倍的原因。由于所有的oring/重新加载,甚至缺少最明显的优化

    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);
    
    我将您的代码放在锁销上的函数中,以获取这些语句的asm

    要评估
    a/b
    gcc-O0
    必须从内存中发出代码来重新加载
    a
    b
    ,而不是使
        # 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