【问题标题】:Why does GCC report a Floating Point Exception when I execute 1/0?为什么执行 1/0 时 GCC 会报告浮点异常?
【发布时间】:2012-02-20 18:13:48
【问题描述】:

Stroustrup 在“C++ 的设计和演变”(Addison Wesley,1994 年)中说,“低级事件,例如算术溢出和除以零,被假定由专用的低级机制处理,而不是这使得 C++ 在算术方面能够与其他语言的行为相匹配。它还避免了在诸如除以零之类的事件是异步的大量流水线架构上发生的问题。”

Q1:如果不是异常,为什么 GCC 会报告一个而不是低级错误?

Q2:给定整数除法,为什么报告为浮点数?

鉴于我无法用 catch(...) 捕获它,这是非常具有误导性的。显然我可以测试并避免整个“错误”,但我的观点是,对于认为它可能是异常(合理)、试图捕捉它、然后发现它不是异常的初学者来说,这非常令人困惑,并想知道报告的运行时异常。

我的编译器是 gcc 版本 4.2.1(Apple Inc. build 5666)(点 3)

对 CPU 异常、FPU 异常、语言异常和操作系统异常之间的区别进行一些准确的说明可能会解决此问题。

示例程序:

int main(){
    int i=1/0;
    return i;
}

结果输出:

浮点异常

【问题讨论】:

  • 这听起来更像是向 Apple 提交的错误报告,而不是问题......
  • 问题是为什么(我的编译器)如此具有误导性。
  • @pst:问题很清楚:为什么“异常”这个词用来表示硬件和软件生成的异常,为什么SIGFPE 同时用于整数和浮点例外?不过,我不知道怎么会有人回答“为什么”。
  • 时间对古老的 unix 信号名称及其描述并不友好。这不是一个可以很好扩展的方案。

标签: c++ exception divide-by-zero


【解决方案1】:

浮点异常 (FPE) 不是 C++ 异常。各种系统中有几种类型的异常,它们是不可互换的。 FPE 在微处理器或 ISA 级别是一个例外,但在 C++ 级别不是。 FPE 可能会触发一个称为 SIGFPE 的信号,您可以处理但不能使用 C++ try/catch。如果你想处理它,你可以使用 POSIX 函数 sigaction(我认为在 Windows 上会使用结构化异常处理)。

【讨论】:

    【解决方案2】:

    C++ 意义上的异常是软件检测到的错误。当您除以零时,它通常是检测问题的硬件并断言硬件异常(相同的名称,相似的概念,不同的野兽)。操作系统的硬件异常处理程序接收到这个并决定做什么。典型的操作系统反应是向硬件异常时正在运行的进程发送信号(如果系统在用户模式下运行),并让该进程的信号处理程序决定如何处理。

    【讨论】:

      【解决方案3】:

      古代历史将浮点错误和除以零都折叠到 SIGFPE 中。虽然您可以在收到异常时对其进行解码以确定哪个异常,但 shell 不会。

      【讨论】:

      • 但这不是 c++ 异常——c++ 不会捕获它。你必须捕获信号。
      • 确实如此。信号处理程序负责处理它。
      【解决方案4】:

      以下小程序捕获一个浮点错误并打印出有关信号的信息。请注意,从 C 语言的角度来看,除以零是未定义的行为。例如,并非所有系统都有 POSIX 信号。因为编译器可以简单地预测错误,它甚至可能在 POSIX 系统上决定简单地删除所有代码,或者立即退出并出现错误。 (我希望并假设 POSIX 系统上的编译器会生成一个执行预期操作的程序。在它下面会执行此操作。但这种希望之前已经落空了。)

      #include <stdio.h>
      #include <signal.h>
      #include <stdlib.h> // for exit()
      
      void fpehandler (int sig, siginfo_t *info, void *uc) 
      {
          fprintf (stderr, 
                  "Caught signal no. %d; code: %d (FPE_INTDIV would be %d)\n", 
                  sig, info->si_code, FPE_INTDIV);
          if(info->si_code == FPE_INTDIV)
          {
              fprintf (stderr, 
                      "Yes, the error was an integer division by zero.\n");
          }
      
          // It's not officially safe to return from a handler
          // of a "program error signal" (of which SIGFPE is an example).
          // Plus many functions are not strictly safe to 
          // call in a signal handler (e.g. exit()).
          // See https://www.securecoding.cert.org/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers .
          // We call _Exit().
          _Exit(0); // success, isn't it?
      }
      
      int main(void)
      {
          struct sigaction sa;
          sigemptyset (&sa.sa_mask);
          sa.sa_flags = SA_SIGINFO;
          sa.sa_sigaction = fpehandler;
          sigaction (SIGFPE, &sa, NULL);
      
          // cause "floating point" error 
          printf("%d\n", 2/0);
      
          // ---- below is unreachable code ;-) ----------
      
          // We shouldn't be here. Something went wrong. return 1.
          return 1;            
      }
      

      当我在 cygwin、gcc 5.4.0 下运行它时,它会打印出来

      $ gcc -Wall float-except.c -o float-except && ./float-except
      float-except.c: In function 'main':
      float-except.c:28:21: warning: division by zero [-Wdiv-by-zero]
           printf("%d\n", 2/0);
                           ^
      Caught signal no. 8; code: 15 (FPE_INTDIV would be 15)
      Yes, the error was an integer division by zero.
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-23
        • 1970-01-01
        • 2015-10-26
        • 1970-01-01
        相关资源
        最近更新 更多