捕获浮点异常的方法取决于体系结构。这是我在Intel (x86) Mac上成功测试过的代码:它取负数的平方根两次,一次在之前,一次在之后,启用浮点异常捕获。第二次调用fpe_signal_handler()
#include <cmath> // for sqrt()
#include <csignal> // for signal()
#include <iostream>
#include <xmmintrin.h> // for _mm_setcsr
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!n";
exit(1);
}
void enable_floating_point_exceptions() {
_mm_setcsr(_MM_MASK_MASK & ~_MM_MASK_INVALID);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "n";
}
使用Xcode提供的apple-clang编译器编译
clang++ -g -std=c++17 -o fpe fpe.cpp
和运行给出以下预期输出:
nan
Floating point exception!
我想写一个类似的程序,在M1 (arm64) Mac上做与上述程序相同的事情。我尝试了以下操作:
#include <cfenv> // for std::fenv_t
#include <cmath> // for sqrt()
#include <csignal> // for signal()
#include <fenv.h> // for fegetenv(), fesetenv()
#include <iostream>
void fpe_signal_handler(int /*signal*/) {
std::cerr << "Floating point exception!n";
exit(1);
}
void enable_floating_point_exceptions() {
std::fenv_t env;
fegetenv(&env);
env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
fesetenv(&env);
signal(SIGFPE, fpe_signal_handler);
}
int main() {
const double x{-1.0};
std::cout << sqrt(x) << "n";
enable_floating_point_exceptions();
std::cout << sqrt(x) << "n";
}
在使用Xcode提供的apple-clang编译器编译后
clang++ -g -std=c++17 -o fpe fpe.cpp
得到以下输出:
nan
zsh: illegal hardware instruction ./fpe
我试着添加-fexceptions
标志,但这并没有什么不同。我注意到ARM编译器工具链"不支持AArch64目标的浮点异常捕获",但我不确定这是否适用于带有苹果工具链的M1 mac电脑。
我是否正确,M1 Mac硬件只是不支持浮点异常捕获?或者有一种方法来修改这个程序,使它捕获第二个浮点异常,然后调用fpe_signal_handler()
?
在同一线程内同步测试异常确实工作得很好,使用来自<fenv.h>
的ISO Cfetestexcept
,就像在cppreference示例中一样。这里的问题是让FP异常实际捕获,以便操作系统提供SIGFPE
,而不仅仅是在FP环境中设置sticky标志。
根据下面的长时间讨论(非常感谢@Peter Cordes的耐心),似乎在Aarch64上的MacOS中,在SIGILL而不是SIGFPE中揭开FP异常然后生成相应的错误FP数学结果。可以在处理程序中检测到信号代码ILL_ILLTRP。
#include <fenv.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
static void
fpe_signal_handler( int sig, siginfo_t *sip, void *scp )
{
int fe_code = sip->si_code;
printf("In signal handler : ");
if (fe_code == ILL_ILLTRP)
printf("Illegal trap detectedn");
else
printf("Code detected : %dn",fe_code);
abort();
}
void enable_floating_point_exceptions()
{
fenv_t env;
fegetenv(&env);
env.__fpcr = env.__fpcr | __fpcr_trap_invalid;
fesetenv(&env);
struct sigaction act;
act.sa_sigaction = fpe_signal_handler;
sigemptyset (&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGILL, &act, NULL);
}
void main()
{
const double x = -1;
printf("y = %fn",sqrt(x));
enable_floating_point_exceptions();
printf("y = %fn",sqrt(x));
}
结果是:
y = nan
In signal handler : Illegal trap detected
Abort trap: 6
其他浮点异常可以用类似的方式检测,例如,取消__fpcr_trap_divbyzero
的掩码,然后生成double x=0; double y=1/x
。如果异常未被揭开,则程序正常终止。
fetestexcept
并不能产生非常可靠的结果。这可能需要更多的调查。
您可以通过将sigaction处理程序的第三个参数强制转换为struct ucontext*
,然后在处理程序中解码scp->uc_mcontext->__es.esr
来解码SIGILL处理程序中FPU异常的性质。这是ESR_ELx, Exception Syndrome Register (ELx)
寄存器的值。如果它的前6位(EC)是0b101100
,那么信号是由捕获AArch64 FPU操作触发的。如果在这种情况下,寄存器(TFV
)的第23位也是1,那么寄存器的低7位将与fpsr寄存器的低7位相匹配,以防陷阱被禁用(即,由指令触发的FPU异常)。
请参阅ARMv8架构手册的D17.2.37
节获取更多信息("ESR_EL1,异常综合征寄存器(EL1)")。