我正在用C为一个uni项目编写一个多线程服务器,并且我很难确定如何以一种很好的,可读的和标准的方式进行错误处理。
现在,如果程序成功终止,我将在程序末尾释放所分配内存的每一位。但是,如果在执行过程中发生致命错误(例如malloc返回NULL
)怎么办?
例如,假设我有一个自定义数据类型mydata_t
和一个构造函数mydata_t *mydata_init()
,供程序的多个模块使用。在互联网上看到一些代码后,这就是我写的样子:
mydata_t *mydata_init() {
mydata_t *mydata = malloc(sizeof(mydata_t));
if (!mydata) return NULL;
mydata->field1 = malloc(sizeof(mydata2_t));
if (!mydata->field1) return NULL;
mydata->field2 = malloc(sizeof(mydata3_t));
if (!mydata->field2) return NULL;
/*
Initialization of other fields
*/
return mydata;
}
它看起来不错,也很干净,但这是“标准”的方式吗?
特别是如果malloc之一返回NULL
怎么办?是否有必要释放所有先前分配的内存?将代码更改为这样的代码是否合理?
mydata_t *mydata_init() {
mydata_t *mydata = malloc(sizeof(mydata_t));
if (!mydata) goto err_1;
mydata->field1 = malloc(sizeof(mydata2_t));
if (!mydata->field1) goto err_2;
mydata->field2 = malloc(sizeof(mydata3_t));
if (!mydata->field2) goto err_3;
/*
Initialization of other fields
*/
return mydata;
/*
Other tags
*/
err_3:
free(mydata->field1);
err_2:
free(mydata);
err_1:
return NULL;
}
[假设一个普通的操作系统,即带有“ kill-9”的操作系统,或者是在GUI上下文菜单上带有“结束进程”条目的任务管理器的操作系统,在着手编写昂贵的大型活动之前,请记住以下几点:特定用户代码,以在发生致命错误时释放所有已分配的/任何内存:
1)用用户代码释放所有内存需要更多用户代码。额外的代码必须经过设计,编码测试和维护,经常在更改和/或新版本之后反复进行。对于复杂的多线程应用程序,也许是在线程之间共享和通信的对象池,甚至尝试使用用户代码关闭它也不是一件容易的事。
2)发生致命错误后,使用用户代码释放所有内存可能会使情况变得更糟。如果错误是堆管理器损坏导致的,那么尝试释放它时将引发更多错误。带有错误日志条目的客户对应用程序的“透支”已经够糟糕的了,充满AV错误框和卡住的应用程序的屏幕更加糟糕。
3)只有在所有其他线程都已停止以使它们无法访问该内存中的任何一个的情况下,只有线程才能使用用户代码安全地释放所有内存。可靠地停止所有进程线程只能由OS在进程终止时完成。用户代码无法保证做到这一点-如果线程在库中执行长时间的操作时卡住了,则无法可靠地停止它。如果尝试这样做,例如,可能会使内存管理器保持锁定状态。仅解除阻塞停留在I / O操作上的线程是非常困难的,常常需要像在本地网络堆栈上打开连接这样的陷阱,以迫使accept()调用尽早返回。
4)操作系统“终止进程” API及其所涉及的所有组件均已通过LOT测试。它可以工作,并且在您的操作系统中免费提供。您试图停止线程和释放内存的用户代码将永远不会累积过多的测试。
5)试图停止线程和释放内存的用户代码是多余的-操作系统执行相同的工作,只是更好,更快和更可靠。您正试图从子分配器中清除内存,而操作系统将很快销毁该子分配器。
6)许多OS和其他商业库已经让位于不可避免的地方,并接受它们无法安全地在关闭时释放所有内存而不会引起问题的问题,尤其是对于多线程应用程序。图书馆作者不能可靠地做到这一点,你也不能。
请确保在运行期间以受控且明智的方式管理您的内存,根据需要释放您的malloc内存,以免在整个过程的生命周期内泄漏内存。
但是,如果遇到致命错误,请尝试将详细信息写入日志文件或执行其他一些调试/日志记录操作,然后调用OS的“终止进程” API。
没有其他可以安全地做的事情。
您的应用快死了,让操作系统对它进行安乐死。
一种可能的选择。
mydata_t *mydata_init()
{
mydata_t *mydata = malloc(sizeof(mydata_t));
if (mydata == NULL)
{
/* Handle error */
return NULL;
}
mydata->field1 = malloc(sizeof(mydata2_t));
mydata->field2 = malloc(sizeof(mydata3_t));
...
if (mydata->field1 != NULL &&
mydata->field2 != NULL &&
...)
{
/* success */
/*
* Initialize everything
*/
return mydata;
}
free(mydata->field1);
free(mydata->field2);
...
free(mydata);
return NULL;
}
注意,在错误路径上调用NULL
之前不需要检查free()
。如this question的第一个答案中所述>
引用C标准,ISO-IEC 9899中的7.20.3.2/2:
如果ptr是空指针,则不会发生任何动作。
是否有必要释放所有先前分配的内存?
现有的答案涵盖了各种malloc / free方案,因此我将不加赘述。