在SIGINT上释放已分配内存的最佳实践

时间:2012-07-14 22:35:38

标签: c memory-management sigint

我有一个简单的程序,使用select和类似的东西来复用IO 为了中断“服务器”过程,我集成了一个sig_handler,它对 SIGINT 作出反应。

每次分配内存时,contains方法都会执行free本身或调用方法。

使用valgrind显示,有些分配没有被释放 也许没有必要,但我想了解处理信号的最佳方法 按 STRG + C 时,似乎未调用free次调用 因此,退出具有中断条件的循环将是毫无意义的,这是我的第一种方法。

在关闭整个程序之前,有没有可能清理所有东西?

感谢任何提示和建议。

3 个答案:

答案 0 :(得分:8)

Valgrind只是一个寻找内存泄漏的工具,而不是一个必须注意其建议的oracle。制作一个程序“Valgrind-clean”是一个有价值的目标,但不要让它失控。问自己一些关于该计划的问题。

  1. 当程序收到SIGINTSIGQUIT或其他什么时,是否需要执行任何操作?是否需要进行某种干净的关机?例如,服务器可能决定完成处理所有打开的请求,或者至少向已连接的客户端发送关闭消息。

  2. 突然终止是否总会留下某些块?然后你就可以从Valgrind中撤消这些报告,而不必花费额外的时间来释放已经被释放的内存。

  3. 简单来说,只有两个理由在即将退出的程序中调用free

    1. 如果这是撤消Valgrind消息的最简单方法(也就是说,没有阅读Valgrind手册)

    2. 如果它使您的代码更简单。

    3. 否则,只是在程序退出期间不要调用free,因为它只会烧掉CPU周期。

      处理SIGINT:我可以想到处理SIGINT的四种常用方法:

      1. 使用默认处理程序。强烈建议,这需要最少量的代码,不太可能导致任何异常的程序行为。您的程序将退出。

      2. 使用longjmp立即退出。这适合喜欢骑无头盔的快速摩托车的人。这就像用图书馆电话玩俄罗斯轮盘赌。不推荐。

      3. 设置一个标志,并中断主循环的pselect / ppoll。这是一个很难做到的痛苦,因为你必须扭动信号面具。您只想中断pselect / ppoll,而不是mallocfree等不可重入的函数,因此您必须非常小心信号掩码等内容。不建议。您必须使用pselect / ppoll代替select / poll,因为“p”版本可以自动设置信号掩码。如果您使用selectpoll,则在您检查国旗后,但在致电select / poll之前,信号可能会到达,这很糟糕。

      4. 创建管道以在主线程和信号处理程序之间进行通信。始终在调用select / poll时加入此管道。信号处理程序只是将一个字节写入管道,如果主循环成功从另一端读取一个字节,则它会干净地退出。强烈推荐。您也可以让信号处理程序自行卸载,因此不耐烦的用户可以点击CTRL+C两次以立即退出。

      5. 两种最简单,最傻瓜的方法是#1和#4。

        退出的程序没有任何泄漏。只有正在运行的程序才会出现泄漏。一旦程序退出,所有内存都被释放(因此不再有泄漏)。

答案 1 :(得分:1)

这是我简单而略显肮脏的解决方案。

#include <signal.h>

volatile bool gContinue; 

void handleCtrlC(int ) {
    gContinue = false;
}

int main () {
    gContinue = true;

    signal(SIGINT, handleCtrlC);

   ... allocate memory ...

    sigset_t sigmask;
    sigemptyset (&sigmask);  

    while (gContinue) {

        /*...*/
        ready = pselect(nfds, &readfds, &writefds, &exceptfds,
            timeout, &sigmask);
        /*...*/
    }

    ... free memory ...

    return 0;
}

编辑:在循环中添加了pselect。

答案 2 :(得分:0)

如果您在致电select之前添加了对该标志的检查,那么Adam Hunt的建议将会有效。必须在此之前,因为正常EINTR处理可能会跳过支票,否则退出将会延迟,直到您的下一个select返回真实事件。

while (gContinue) {
    /*set up some stuff for next select call*/
    do {
        if (gContinue == 0) break;
        nfds = select(...);
    } while (nfds == -1 && errno == EINTR);
    /*handle select return*/
}

编辑:Dietrich Epp指出,国旗支票和select来电之间存在竞争条件,只能通过拨打pselect来关闭。 pselectselect非常相似,主要区别在于最后一个参数用作掩码来确定要阻止的信号。因此,下面的代码示例将关闭标记检查和pselect调用之间的竞争:

sigset_t emptyset, blockset, origset;
sigemptyset(&emptyset);
sigemptyset(&blockset);
sigaddset(&blockset, SIGINT);

while (gContinue) {
    /*...*/
    sigprocmask(SIG_BLOCK, &blockset, &origset);
    do {
        if (gContinue == 0) break;
        nfds = pselect(..., &emptyset);
    } while (nfds == -1 && errno == EINTR);
    sigprocmask(SIG_SETMASK, &origset, NULL);
    /*...*/
};

另一种方法是将所有已分配的元素注册到全局数据结构,以便可以通过您安装的atexit处理程序释放它们。如果您最终会使用解除分配的代码,请先从全局数据结构中取消注册;

m = malloc(sz);
register_allocation(m);
/*...*/
unregister_allocation(m);
free(m);

使用atexit

void cleanup_allocations () {
    /*...*/
}

atexit(cleanup_allocations);