我有一个简单的程序,使用select
和类似的东西来复用IO
为了中断“服务器”过程,我集成了一个sig_handler,它对 SIGINT 作出反应。
每次分配内存时,contains方法都会执行free本身或调用方法。
使用valgrind
显示,有些分配没有被释放
也许没有必要,但我想了解处理信号的最佳方法
按 STRG + C 时,似乎未调用free
次调用
因此,退出具有中断条件的循环将是毫无意义的,这是我的第一种方法。
在关闭整个程序之前,有没有可能清理所有东西?
感谢任何提示和建议。
答案 0 :(得分:8)
Valgrind只是一个寻找内存泄漏的工具,而不是一个必须注意其建议的oracle。制作一个程序“Valgrind-clean”是一个有价值的目标,但不要让它失控。问自己一些关于该计划的问题。
当程序收到SIGINT
或SIGQUIT
或其他什么时,是否需要执行任何操作?是否需要进行某种干净的关机?例如,服务器可能决定完成处理所有打开的请求,或者至少向已连接的客户端发送关闭消息。
突然终止是否总会留下某些块?然后你就可以从Valgrind中撤消这些报告,而不必花费额外的时间来释放已经被释放的内存。
简单来说,只有两个理由在即将退出的程序中调用free
。
如果这是撤消Valgrind消息的最简单方法(也就是说,没有阅读Valgrind手册)
如果它使您的代码更简单。
否则,只是在程序退出期间不要调用free
,因为它只会烧掉CPU周期。
处理SIGINT:我可以想到处理SIGINT的四种常用方法:
使用默认处理程序。强烈建议,这需要最少量的代码,不太可能导致任何异常的程序行为。您的程序将退出。
使用longjmp
立即退出。这适合喜欢骑无头盔的快速摩托车的人。这就像用图书馆电话玩俄罗斯轮盘赌。不推荐。
设置一个标志,并中断主循环的pselect
/ ppoll
。这是一个很难做到的痛苦,因为你必须扭动信号面具。您只想中断pselect
/ ppoll
,而不是malloc
或free
等不可重入的函数,因此您必须非常小心信号掩码等内容。不建议。您必须使用pselect
/ ppoll
代替select
/ poll
,因为“p”版本可以自动设置信号掩码。如果您使用select
或poll
,则在您检查国旗后,但在致电select
/ poll
之前,信号可能会到达,这很糟糕。
创建管道以在主线程和信号处理程序之间进行通信。始终在调用select
/ poll
时加入此管道。信号处理程序只是将一个字节写入管道,如果主循环成功从另一端读取一个字节,则它会干净地退出。强烈推荐。您也可以让信号处理程序自行卸载,因此不耐烦的用户可以点击CTRL+C
两次以立即退出。
两种最简单,最傻瓜的方法是#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
来关闭。 pselect
与select
非常相似,主要区别在于最后一个参数用作掩码来确定要阻止的信号。因此,下面的代码示例将关闭标记检查和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);