发生错误后,我是否必须释放任何先前分配的内存?

时间:2018-08-01 00:41:21

标签: c memory-management error-handling goto

我正在用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;

}

4 个答案:

答案 0 :(得分:1)

一种可能的选择。

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是空指针,则不会发生任何动作。

答案 1 :(得分:1)

  

是否有必要释放所有先前分配的内存?

不,但是非泄漏(好的)代码可以。

您会发现关于如何做到这一点(腾出空间)的各种意见,但最终目标是某种方式。释放未使用的资源。

代码清晰明了。

注意:goto形式是goto的可接受用法。


我将提供另一种方法,并尽可能使用mydata_uninit()伴随函数。

mydata_t *mydata_uninit(mydata_t *mydata) {
  if (mydata) {
    free(mydate->field1); 
    mydate->field1 = NULL; // example discussed below **
    free(mydate->field2); 
    // free other resources and perform other clean up/accounting.
    free(mydate); 
  }
  return NULL;
}

我还将分配给取消引用的指针的大小,而不是类型。

mydata_t *mydata_init(void) {
  mydata_t *mydata = calloc(1, sizeof *mydata);
  if (mydata == NULL) {
    return NULL;
  }   
  mydata->field1 = calloc(1, sizeof *(mydata->field1));
  mydata->field2 = calloc(1, sizeof *(mydata->field2));
  // Other 1st time assignments

  // If any failed
  if (mydata->field1 == NULL || mydata->field2 == NULL) {
    mydata = mydata_uninit(mydata);
  }

  return mydata;
}

**将指针struct成员设置为NULLmydata->field1),然后释放struct指针(mydata),我发现在调试方面有帮助取消引用NULL指针的错误代码通常比释放指针要快。

答案 2 :(得分:1)

现有的答案涵盖了各种malloc / free方案,因此我将不再赘述。

我想指出的是,如果您失败了malloc,那么该程序将面临很大的挑战,通常,exit()比尝试恢复这种情况要好。 / p>

即使您设法清理部分分配的结构,其他代码以及该代码使用的库,也将无法分配内存,并且可能(而且经常会)以神秘的方式失败。

malloc()通常仅在以下情况下才会失败:如果您在严格受限的环境中运行(例如嵌入式微控制器),或者正在像筛一样泄漏内存。

答案 3 :(得分:0)

假设一个不平凡的操作系统(即带有GUI菜单上带有“ kill-9”的任务管理器或带有“ End Process”条目的任务管理器),在着手进行一项昂贵的大型活动之前,请记住以下几点:编写特定的用户代码,以在发生致命错误时释放所有已分配的所有内存:

1)使用用户代码释放所有内存需要更多用户代码。额外的代码必须进行设计,编码测试和维护,经常在更改和/或新版本后重复进行。有了一个复杂的多线程应用程序,也许有了在线程之间共享和通信的对象池,甚至尝试使用用户代码关闭它也不是一件容易的事。

2)发生致命错误后,使用用户代码释放所有内存可能会使情况变得更糟。如果错误是堆管理器损坏导致的,那么在尝试释放它时会引发更多错误。带有错误日志条目的客户对应用程序的“透支”已经足够糟糕,充满AV错误框且卡住的应用程序的屏幕更加糟糕。

3)只有在所有其他线程都已停止的情况下,一个线程才能安全释放用户代码的所有内存,以使它们无法访问该内存中的任何一个。可靠地停止所有进程线程只能由OS在进程终止时完成。用户代码不能保证做到这一点-如果线程在执行库中执行冗长的操作时卡住了,则无法可靠地停止它。如果尝试这样做,例如,可能会使内存管理器保持锁定状态。仅仅释放阻塞在I / O操作上的线程是非常困难的,常常需要像在本地网络堆栈上打开连接这样的陷阱来强制accept()调用提前返回。

4)OS的“终止进程” API及其所涉及的所有组件都已经过大量测试。它可以工作,并且在您的操作系统中免费提供。您尝试停止线程和释放内存的用户代码将永远不会累积过多的测试。

5)试图停止线程和释放内存的用户代码是多余的-操作系统执行相同的工作,只是更好,更快和更可靠。您正在尝试从子分配器清理内存,而该子分配器将使OS很快销毁并取消分配。

6)许多操作系统和其他商业库已经让位于不可避免的位置,并接受它们不能安全地在关闭时释放所有内存而不会引起问题的问题,尤其是对于多线程应用程序。图书馆作者不能可靠地做到这一点,你也不能。

当然,请在运行期间以受控且明智的方式管理内存,以释放所需的malloc内存,以免在进程的生存期内泄漏内存。

但是,如果遇到致命错误,请尝试将详细信息写入日志文件或进行一些其他调试/记录操作,然后调用操作系统的“终止进程” API。

您别无选择。

您的应用快要死了,让操作系统对它进行安乐死。