从捕获浮点异常返回
Returning From Catching A Floating Point Exception
因此,我试图从浮点异常中 return,但我的代码却一直在循环。我实际上可以退出进程,但我想做的是return并重做导致浮点错误的计算
FPE 发生的原因是因为我有一个随机数生成器可以生成多项式的系数。使用一些 LAPACK 函数,我求解根并做一些其他事情。在此数学密集型链中的某处,发生了浮点异常。发生这种情况时,我想做的是增加随机数生成器的状态,然后重试,直到系数不会出现错误为止,因为它通常不会出现,但很少出现并导致灾难性的结果。
所以我写了一个简单的测试程序来学习如何使用信号。如下:
在exceptions.h
#ifndef EXCEPTIONS_H
#define EXCEPTIONS_H
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <math.h>
#include <errno.h>
#include <float.h>
#include <fenv.h>
void overflow_handler(int);
#endif // EXCEPTIONS_H //
在exceptions.c
#include "exceptions.h"
void overflow_handler(int signal_number)
{
if (feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID)){
fprintf(stdout, "Nothing Cleared!\n");
}
else{
fprintf(stdout, "All Cleared!\n");
}
return;
}
在main.c
#include "exceptions.h"
int main(void)
{
int failure;
float oops;
//===Enable Exceptions===//
failure = 1;
failure = feenableexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID);
if (failure){
fprintf(stdout, "FE ENABLE EXCEPTIONS FAILED!\n");
}
//===Create Error Handler===//
signal(SIGFPE, overflow_handler);
//===Raise Exception===//
oops = exp(-708.5);
fprintf(stdout, "Oops: %f\n", oops);
return 0;
}
Makefile
#===General Variables===#
CC=gcc
CFLAGS=-Wall -Wextra -g3 -Ofast
#===The Rules===#
all: makeAll
makeAll: makeExceptions makeMain
$(CC) $(CFLAGS) exceptions.o main.o -o exceptions -ldl -lm
makeMain: main.c
$(CC) $(CFLAGS) -c main.c -o main.o
makeExceptions: exceptions.c exceptions.h
$(CC) $(CFLAGS) -c exceptions.c -o exceptions.o
.PHONY: clean
clean:
rm -f *~ *.o
当我清除异常时,为什么这个程序没有终止,应该是成功的?我需要做什么才能 return 进入主界面并退出?
如果我能做到这一点,我可以在 returning 和退出之间放置代码,并在 FPE 被捕获后做一些事情。我想我会设置某种标志,然后清除数据结构中的所有最新信息,根据是否设置该标志重做计算等。关键是,真正的程序不能中止或永远循环,而是必须处理异常并继续运行。
帮忙?
"division by zero"、overflow/underflow 等首先会导致 未定义的行为 。但是,如果系统为此生成信号,则 UB 的效果为 "suspended"。信号处理程序接管。但是如果handlerreturns,UB的效果会"resume"。
因此,标准不允许从这种情况返回。
试想一下:程序将如何从例如DIV0?抽象机不知道 FPU 寄存器或状态标志,即使 - 必须生成什么结果?
C 也没有像 C++ 那样正确展开堆栈的规定。
另请注意,为算术异常生成信号是可选的,因此无法保证会实际生成信号。处理程序主要用于通知事件并可能清理外部资源。
对于不是源自未定义行为但只是中断程序执行的信号,行为是不同的。这是明确定义的,因为程序状态是明确定义的。
编辑:
如果你必须依赖程序在所有情况下继续,你必须在进行实际操作之前检查算术运算的所有参数and/or仅使用安全操作(重新排序,使用更大的中间值类型等)。整数的一个例子可能是使用 unsigned
而不是 signed
整数,因为那些溢出行为是明确定义的(换行),因此只要更正中间结果溢出就不会造成麻烦之后,包装不是太多。 (免责声明:当然,这并不总是有效)。
更新:
虽然我仍然不完全确定,但根据评论,标准可能允许,至少对于 托管环境,使用 LIA-1 陷阱并从中恢复(参见 Annex H。由于这些不一定准确,我怀疑在所有情况下都不可能恢复。此外,math.h
可能会出现其他方面,需要仔细评估。
最后:我仍然认为这种方法没有任何好处,但与使用安全算法相比增加了一些不确定性。如果不涉及那么多不同的组件,情况会有所不同。对于裸机嵌入式系统,视图可能完全不同。
我认为如果你想跳过一条指令或跳出 exp
或其他什么,你应该弄乱调用堆栈帧。这是高级巫术,肯定无法携带。
GNU C 库 lets you 在信号处理程序外部使用 setjmp()
,您可以从内部 longjmp()
使用它。这似乎是一个更好的方法。这是对您的程序的独立修改,显示了如何执行此操作:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <math.h>
#include <errno.h>
#include <float.h>
#include <fenv.h>
sigjmp_buf oh_snap;
void overflow_handler(int signal_number) {
if (feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID)){
fprintf(stdout, "Nothing Cleared!\n");
}
else{
fprintf(stdout, "All Cleared!\n");
}
siglongjmp(oh_snap, 1);
return;
}
int main(void) {
int failure;
float oops;
failure = 1;
failure = feenableexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID);
if (failure){
fprintf(stdout, "FE ENABLE EXCEPTIONS FAILED!\n");
}
signal(SIGFPE, overflow_handler);
if (sigsetjmp(oh_snap, 1)) {
printf("Oh snap!\n");
} else {
oops = exp(-708.5);
fprintf(stdout, "Oops: %f\n", oops);
}
return 0;
}
因此,我试图从浮点异常中 return,但我的代码却一直在循环。我实际上可以退出进程,但我想做的是return并重做导致浮点错误的计算
FPE 发生的原因是因为我有一个随机数生成器可以生成多项式的系数。使用一些 LAPACK 函数,我求解根并做一些其他事情。在此数学密集型链中的某处,发生了浮点异常。发生这种情况时,我想做的是增加随机数生成器的状态,然后重试,直到系数不会出现错误为止,因为它通常不会出现,但很少出现并导致灾难性的结果。
所以我写了一个简单的测试程序来学习如何使用信号。如下:
在exceptions.h
#ifndef EXCEPTIONS_H
#define EXCEPTIONS_H
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <math.h>
#include <errno.h>
#include <float.h>
#include <fenv.h>
void overflow_handler(int);
#endif // EXCEPTIONS_H //
在exceptions.c
#include "exceptions.h"
void overflow_handler(int signal_number)
{
if (feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID)){
fprintf(stdout, "Nothing Cleared!\n");
}
else{
fprintf(stdout, "All Cleared!\n");
}
return;
}
在main.c
#include "exceptions.h"
int main(void)
{
int failure;
float oops;
//===Enable Exceptions===//
failure = 1;
failure = feenableexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID);
if (failure){
fprintf(stdout, "FE ENABLE EXCEPTIONS FAILED!\n");
}
//===Create Error Handler===//
signal(SIGFPE, overflow_handler);
//===Raise Exception===//
oops = exp(-708.5);
fprintf(stdout, "Oops: %f\n", oops);
return 0;
}
Makefile
#===General Variables===#
CC=gcc
CFLAGS=-Wall -Wextra -g3 -Ofast
#===The Rules===#
all: makeAll
makeAll: makeExceptions makeMain
$(CC) $(CFLAGS) exceptions.o main.o -o exceptions -ldl -lm
makeMain: main.c
$(CC) $(CFLAGS) -c main.c -o main.o
makeExceptions: exceptions.c exceptions.h
$(CC) $(CFLAGS) -c exceptions.c -o exceptions.o
.PHONY: clean
clean:
rm -f *~ *.o
当我清除异常时,为什么这个程序没有终止,应该是成功的?我需要做什么才能 return 进入主界面并退出?
如果我能做到这一点,我可以在 returning 和退出之间放置代码,并在 FPE 被捕获后做一些事情。我想我会设置某种标志,然后清除数据结构中的所有最新信息,根据是否设置该标志重做计算等。关键是,真正的程序不能中止或永远循环,而是必须处理异常并继续运行。
帮忙?
"division by zero"、overflow/underflow 等首先会导致 未定义的行为 。但是,如果系统为此生成信号,则 UB 的效果为 "suspended"。信号处理程序接管。但是如果handlerreturns,UB的效果会"resume"。
因此,标准不允许从这种情况返回。
试想一下:程序将如何从例如DIV0?抽象机不知道 FPU 寄存器或状态标志,即使 - 必须生成什么结果?
C 也没有像 C++ 那样正确展开堆栈的规定。
另请注意,为算术异常生成信号是可选的,因此无法保证会实际生成信号。处理程序主要用于通知事件并可能清理外部资源。
对于不是源自未定义行为但只是中断程序执行的信号,行为是不同的。这是明确定义的,因为程序状态是明确定义的。
编辑:
如果你必须依赖程序在所有情况下继续,你必须在进行实际操作之前检查算术运算的所有参数and/or仅使用安全操作(重新排序,使用更大的中间值类型等)。整数的一个例子可能是使用 unsigned
而不是 signed
整数,因为那些溢出行为是明确定义的(换行),因此只要更正中间结果溢出就不会造成麻烦之后,包装不是太多。 (免责声明:当然,这并不总是有效)。
更新:
虽然我仍然不完全确定,但根据评论,标准可能允许,至少对于 托管环境,使用 LIA-1 陷阱并从中恢复(参见 Annex H。由于这些不一定准确,我怀疑在所有情况下都不可能恢复。此外,math.h
可能会出现其他方面,需要仔细评估。
最后:我仍然认为这种方法没有任何好处,但与使用安全算法相比增加了一些不确定性。如果不涉及那么多不同的组件,情况会有所不同。对于裸机嵌入式系统,视图可能完全不同。
我认为如果你想跳过一条指令或跳出 exp
或其他什么,你应该弄乱调用堆栈帧。这是高级巫术,肯定无法携带。
GNU C 库 lets you 在信号处理程序外部使用 setjmp()
,您可以从内部 longjmp()
使用它。这似乎是一个更好的方法。这是对您的程序的独立修改,显示了如何执行此操作:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <math.h>
#include <errno.h>
#include <float.h>
#include <fenv.h>
sigjmp_buf oh_snap;
void overflow_handler(int signal_number) {
if (feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID)){
fprintf(stdout, "Nothing Cleared!\n");
}
else{
fprintf(stdout, "All Cleared!\n");
}
siglongjmp(oh_snap, 1);
return;
}
int main(void) {
int failure;
float oops;
failure = 1;
failure = feenableexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_DIVBYZERO | FE_INVALID);
if (failure){
fprintf(stdout, "FE ENABLE EXCEPTIONS FAILED!\n");
}
signal(SIGFPE, overflow_handler);
if (sigsetjmp(oh_snap, 1)) {
printf("Oh snap!\n");
} else {
oops = exp(-708.5);
fprintf(stdout, "Oops: %f\n", oops);
}
return 0;
}