如何在OS X中为clang启用浮点异常?

4
我希望我的代码在出现浮点错误时能够终止。在linux-gcc中,使用“feenableexcept()”函数可以完成此操作,但这在OSX上不可用。在OS X上使用gcc时,采用(Enabling floating point interrupts on Mac OS X Intel)中的方法可以正常工作,但是在使用clang时无法生效。
示例代码:
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <iostream>
#include <xmmintrin.h>

void handler(int sig) {
  void *array[10];
  size_t size;
  size = backtrace(array, 10);
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

int main(int argc, char **argv)
{
  _MM_SET_EXCEPTION_MASK( ( _MM_EXCEPT_INVALID |
                _MM_EXCEPT_DENORM |
                _MM_EXCEPT_DIV_ZERO |
                _MM_EXCEPT_OVERFLOW |
                _MM_EXCEPT_UNDERFLOW |
                _MM_EXCEPT_INEXACT ) );

  signal(SIGSEGV, handler);
  signal(SIGFPE, handler);

  std::cout<<"Perform 1.0/0.0"<<std::endl;
  double a = 1.0/0.0;
  std::cout<<"1.0/0.0 didn't kill program, result is "<<a<<std::endl<<std::endl;

  int* foo = (int*) - 1 ;// make a bad pointer
  std::cout<<"Attempting to print a bad pointer"<<std::endl;
  printf("%d\n", *foo);
  std::cout<<"Bad pointer didn't kill program."<<std::ends;
}

使用gcc5进行编译,结果为:

Perform 1.0/0.0
Error: signal 8:
0   a.out                               0x000000010f97cb7f _Z7handleri + 28
1   libsystem_platform.dylib            0x00007fff895c652a _sigtramp + 26
2   ???                                 0x00007fff6eab6568 0x0 + 140735050114408
3   libdyld.dylib                       0x00007fff936a15ad start + 1

很好。非常有效。然而,当使用clang编译(Apple LLVM版本7.3.0(clang-703.0.29)),结果是这样的:

Perform 1.0/0.0
1.0/0.0 didn't kill program, result is inf

Attempting to print a bad pointer
Error: signal 11:
0   a.out                               0x000000010d501d1f _Z7handleri + 31
1   libsystem_platform.dylib            0x00007fff895c652a _sigtramp + 26
2   ???                                 0x00007fff62b7e568 0x0 + 140734849607016
3   libdyld.dylib                       0x00007fff936a15ad start + 1

效果不太好。没有触发FPE,代码仅继续运行。我已经查找了一些资料,但无法找到如何让clang触发FPE的方法。有人有相关经验吗?谢谢!


可能是在Mac OS X Intel上启用浮点中断的重复问题。 - nwellnhof
@nwellnhof,链接问题中的方法在clang上不起作用。 - doc07b5
2个回答

2
接受的方法似乎是在项目的内置包含文件中添加一个新的头文件。对于Linux来说,该文件将被忽略并简单地回退到原始的fenv.h库文件。
这段代码的来源为“2009年,David N. Williams”,直接从ardupilot项目中借用而来,这也是我能找到的第一个结果。我可以确认这在MacOS 10.13上使用Apple LLVM version 9.0.0 (clang-900.0.39.2)是有效的。
注意:虽然我看到了许多建议为了清晰起见给这个头文件命名一个独特的名称,但在许多方面,它让我想起了JS/webdev中的polyfill(术语),因此,借用代码的项目以及我的项目都将其命名为fenv.h。根据需要进行重命名。

fenv.h

#pragma once

#include_next <fenv.h>

#if defined(__APPLE__) && defined(__MACH__)

// Public domain polyfill for feenableexcept on OS X
// http://www-personal.umich.edu/~williams/archive/computation/fe-handling-example.c

inline int feenableexcept(unsigned int excepts)
{
    static fenv_t fenv;
    unsigned int new_excepts = excepts & FE_ALL_EXCEPT;
    // previous masks
    unsigned int old_excepts;

    if (fegetenv(&fenv)) {
        return -1;
    }
    old_excepts = fenv.__control & FE_ALL_EXCEPT;

    // unmask
    fenv.__control &= ~new_excepts;
    fenv.__mxcsr   &= ~(new_excepts << 7);

    return fesetenv(&fenv) ? -1 : old_excepts;
}

inline int fedisableexcept(unsigned int excepts)
{
    static fenv_t fenv;
    unsigned int new_excepts = excepts & FE_ALL_EXCEPT;
    // all previous masks
    unsigned int old_excepts;

    if (fegetenv(&fenv)) {
        return -1;
    }
    old_excepts = fenv.__control & FE_ALL_EXCEPT;

    // mask
    fenv.__control |= new_excepts;
    fenv.__mxcsr   |= new_excepts << 7;

    return fesetenv(&fenv) ? -1 : old_excepts;
}

#endif

非常感谢您的知识。我已使用MacOSX 10.14 Mojave和Apple LLVM中的g ++测试了上述代码,但对我来说无效。相比之下,当我使用Homebew安装的g ++ v8.3测试上述代码时,它可以工作。您有什么想法可以使代码在Apple LLVM中运行吗? - tatsy
@tatsy 我已经升级到Mojave,但尚未尝试运行上述补丁。如果我遇到问题,我会提供反馈。我也鼓励其他人这样做。 - tresf

0

您设置了掩码异常。因此,如果您想获取异常,必须清除该位:

_MM_SET_EXCEPTION_MASK( _MM_GET_EXCEPTION_MASK() 
       & ~( _MM_EXCEPT_INVALID |
            _MM_EXCEPT_DENORM |
            _MM_EXCEPT_DIV_ZERO |
            _MM_EXCEPT_OVERFLOW |
            _MM_EXCEPT_UNDERFLOW |
            _MM_EXCEPT_INEXACT ) );

1
将此答案复制到代码中,对于g ++或clang ++都无法正常工作。它对您有效吗?您是否知道任何介绍位字段如何工作的入门级参考资料? - doc07b5
我在浏览器中输入了它,所以我没有测试它。你没有收到异常吗?你可以通过谷歌搜索“_MM_SET_EXCEPTION_MASK”来找到参考资料。你说的“这些位域是怎么工作的”是什么意思?是一般性的还是特别指异常掩码? - Amin Negm-Awad
我没有收到浮点异常。我已经搜索了_MM_SET_EXCEPTION_MASK,并且找到的方法适用于gcc,但不适用于clang。我所说的“这些位域如何工作”是指位域如何解释?是否有位域映射?它是平台相关还是编译器相关?我认为这应该是很容易找到的东西,但我找不到任何东西。也许我只是应该知道这个? - doc07b5
正如您所知,所有的值都是以位的组合形式存储的。通常这些位被解释为整数或浮点数。使用位域,每个位都被单独解释。设置或清除一个位会设置或清除特定的选项。要组合选项,使用位或运算符 |,要读取特定位,使用位与运算符 &~ 反转所有位。 - Amin Negm-Awad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接