处理信号SIGFPE并继续执行的C语言代码

7
我正在尝试处理SIGFPE信号,但我的程序会崩溃或者一直运行。我必须使用signal()而不是其他的函数,比如sigaction()
因此,在我的代码中,我有以下内容:
#include <stdio.h>
#include <signal.h>

void handler(int signum)
{
    // Do stuff here then return to execution below
}

int main()
{
    signal(SIGFPE, handler);

    int i, j;
    for(i = 0; i < 10; i++) 
    {
        // Call signal handler for SIGFPE
        j = i / 0;
    }

    printf("After for loop");

    return 0;
}

基本上,每当发生除以0的情况时,我希望进入处理程序。 它应该在handler()函数内部执行必要的操作,然后继续下一次循环迭代。

这也适用于需要处理的其他信号。任何帮助都将不胜感激。


发布的代码无法编译。也许如果您编辑必要的#include语句,就可以编译成功。然而,编译器会输出关于变量设置但未使用等消息。 - user3629249
相关链接:https://dev59.com/r2s05IYBdhLWcg3wR_63 - Ciro Santilli OurBigBook.com
2个回答

5
如果您必须使用信号来处理FPE或任何其他由您直接调用导致的信号,那么只有在从信号处理程序退出程序或使用longjmp跳出时才定义了会发生什么。
此外,请注意恢复函数的确切放置位置,在计算分支的末尾但在处理分支的开头。
不幸的是,您根本无法像这样使用signal();第二次调用会导致代码崩溃。如果您打算处理信号超过一次,则必须使用sigaction。
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>

jmp_buf fpe;

void handler(int signum)
{
    // Do stuff here then return to execution below
    longjmp(fpe, 1);
}

int main()
{
    volatile int i, j;
    for(i = 0; i < 10; i++) 
    {
        // Call signal handler for SIGFPE
        struct sigaction act;
        struct sigaction oldact;
        memset(&act, 0, sizeof(act));
        act.sa_handler = handler;
        act.sa_flags = SA_NODEFER | SA_NOMASK;
        sigaction(SIGFPE, &act, &oldact);

        if (0 == setjmp(fpe))
        {
            j = i / 0;
            sigaction(SIGFPE, &oldact, &act);
        } else {
            sigaction(SIGFPE, &oldact, &act);
            /* handle SIGFPE */
        }
    }

    printf("After for loop");

    return 0;
}

嗨,当我运行这个程序时,我会收到“error: unknown type name ‘sighandler_t’”的错误提示。我使用gcc进行编译。 - syy
@Flow;请查阅您的编译器手册以了解信号,它似乎是非标准的。 - Joshua
我正在Ubuntu 14.04服务器版本中使用“gcc(Ubuntu 4.8.4-2ubuntu1〜14.04.3)4.8.4”。当我执行“man signal”时,我看到了其中的“sighandler_t”。不确定还有什么需要做的。 - syy
1
是的,谢谢!我不得不自己添加定义。但现在它从未打印“After for loop”。我只打印了一次“浮点异常(核心已转储)”,然后退出。 - syy
@JonathanLeffler:我并不是恢复SIG_DFL本身,而是恢复调用函数已经设置好的任何内容。 - Joshua
显示剩余7条评论

5

注意: 很抱歉打破好事,但是你确实不想这样做。

捕获类似SIGINT, SIGTERM, SIGHUP等[外部产生的]信号是完全有效的,以允许程序优雅地清理和终止可能有文件处于部分写入状态的程序。

但是,内部产生的信号,如SIGILL、SIGBUS、SIGSEGV SIGFPE很难有意义地从中恢复。前三者都是纯粹的错误。而且,在我看来,SIGFPE也是一个处理的错误。

在此类信号之后,您的程序处于不安全不确定的状态。即使捕获信号并使用longjmp/siglongjmp也无法解决此问题。

而且,没有办法准确地知道损坏有多严重。或者,如果程序尝试继续运行,那么损坏会变得多么严重。

如果收到SIGFPE,它是由浮点计算引起的[您可能可以解决]还是由整数除以零引起的?这个计算在哪里进行?您不知道。

尝试继续运行有时会导致10倍的损坏,因为现在程序已经失控。恢复后,程序可能没问题,但也可能有问题。因此,在事件之后,程序的可靠性无法确定。

是什么事件(即)导致了SIGFPE的计算?也许,这不仅仅是一个单独的除法,而是导致值为零的一系列计算。这些值去哪了?这些现在可疑的值是否会被恢复操作后的代码使用?

例如,程序可能会覆盖错误的文件,因为失败的计算某种方式涉及选择调用者将要使用的文件描述符。

或者,您会泄漏内存。或者,破坏堆。或者,错误是否在堆分配代码本身中?

考虑以下函数:

void
myfunc(char *file)
{
    int fd;

    fd = open(file,O_WRONLY);

    while (1) {
        // do stuff ...

        // write to the file
        write(fd,buf,len);

        // do more stuff ...

        // generate SIGFPE ...
        x = y / z;
    }

    close(fd);
}

即使使用一个执行 siglongjmp 的信号处理器,myfunc 写入的文件现在已经损坏/截断。而且,文件描述符不会被关闭。
或者,如果 myfunc 正在从文件中读取并将数据保存到某个数组中。那么该数组只被部分填充。现在,你会收到 SIGFPE。这被信号处理器拦截,它执行了 siglongjmpmyfunc 的调用者之一通过执行 sigsetjmp 来“捕获”这个问题。但是,它能做什么呢?调用者不知道情况有多糟糕。它可能会假设 myfunc 读取的缓冲区已经完全形成,并将其写入另一个文件。那个文件现在已经损坏了。

更新:

哎呀,忘记提到 未定义行为 了...

通常,我们将 UB(例如越过数组末尾的写操作)与 segfault [SIGSEGV] 相关联。但是,如果它导致 SIGFPE 呢?

这不再仅仅是“错误的计算”——我们在最早的检测点捕获[并忽略]了 UB。如果我们进行恢复,下一次使用可能会更糟。

这里有一个例子:

// assume these are ordered in memory as if they were part of the same struct:
int x[10];
int y;
int z;

void
myfunc(void)
{

    // initialize
    y = 23;
    z = 37;

    // do stuff ...

    // generate UB -- we run one past the end of x and zero out y
    for (int i = 0;  i <= 10;  ++i)
        x[i] = 0;

    // do more stuff ...

    // generate SIGFPE ...
    z /= y;

    // do stuff ...

    // do something _really_ bad with y that causes a segfault or _worse_
    // sends a space rocket off-course ...
}

2
非常正确 - 尝试从SIGILL,SIGBUS,SIGFPE,SIGSEGV中恢复几乎总是充满风险、不可靠且可能导致问题。现在,SIGFPE几乎只会因为整数除以零而引发;浮点除以零通常返回无穷大。 - Jonathan Leffler
@JonathanLeffler 它是基于 GNU pth https://www.gnu.org/software/pth/ 吗?我能找到的最早的变更日志条目是1999年,所以可能不是同一个。在我的回答中:https://dev59.com/k1kT5IYBdhLWcg3wALDw#39185831 我所说的 LWP 实际上是 GNU pth [在使用 NPTL 之前,我曾经使用过它,但是记不起名字了]。 - Craig Estey
@JosephGarvin你的负评并不符合负评答案的准则(即_极其_错误)。而且,我发布了许多“不要那样做”的答案[像其他回答者一样],它们并没有被负评。在大多数情况下,OPs都会对这些答案表示感激。有时候,做一些通常不建议的事情是可以的[如果需要,我经常这样做],但这里不是一个好的例子。而且你无法恢复,因为你不知道需要调整哪个值来防止进一步损坏。 - Craig Estey
2
如果指南没有明确说明答案应该实际回答问题,那么这些指南就是有缺陷的。在stackoverflow上,人们总是倾向于说提问者不应该有他们需要的需求,这是其中最令人烦恼的方面之一。如果您有少数派使用案例,您会得到大量像您这样的帖子,告诉您不应该需要您需要的东西,通常是来自对您的要求没有见解的人。 - Joseph Garvin
@JosephGarvin 这是一个关于某个答案是否回答了问题的观点问题。但是在这里,我的答案确实回答了问题,因为 OP 正在尝试做的事情在任何有意义的方式下都是不可能的。我希望你能读完整个答案,看看为什么。而且,这得到了支持,因为我已经编写了代码,必须处理三个商业产品的关键任务[不能失败]。 - Craig Estey
显示剩余6条评论

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