繁体   English   中英

我如何正确处理C中的malloc失败,尤其是当有多个malloc时?

[英]How can I correctly handle malloc failure in C, especially when there is more than one malloc?

假设这是我的代码的一部分:

 int foo()
 {  
    char *p, *q ;
    if((p = malloc(BUFSIZ)) == NULL) {
        return ERROR_CODE;
    }
    if((q = malloc(BUFSIZ)) == NULL) {
        free(p)
        return ERROR_CODE;
    }
    /* Do some other work... */

    free(p);
    free(q);  
 }

由于第一个malloc可能成功而第二个malloc失败,所以我在第二个“错误处理程序”中使用free(p) 但是,如果还有更多的malloc呢?如果我想修改代码(调整它们的顺序,添加或删除一些malloc )怎么办?

我知道在C ++中有诸如RAII和异常安全之类的东西。但是总的来说,在C中处理malloc失败的正确方法是什么? (也许使用一些goto吗?)

您的代码很好,但是对于许多变量,我希望:

int
foo()
{
    char *p = NULL;
    char *q = NULL;
    int ret = 0;

    if (NULL == (p = malloc(BUFSIZ)))
    {
        ret = ERROR_CODE;
        goto error;
    }

    // possibly do something here

    if (NULL == (q = malloc(BUFSIZ)))
    {
        ret = ERROR_CODE;
        goto error;
    }

    // insert similar repetitions

    // hopefully do something here

  error:
    free (p);
    free (q);
    return ret;
}

请注意,将NULL释放定义为无操作。

这避免了n n变量的n个缩进级别。 您可以类似地清理文件句柄等(尽管您必须在close()周围放置一个条件)。

现在,如果您知道可以一次分配它们,那么dasblinkenlight有一个很好的答案,但是这是另一种方法:

int
foo()
{
    int ret = 0;
    char *p = malloc(BUFSIZ);
    char *q = malloc(BUFSIZ);
    char *r = malloc(BUFSIZ);
    if (!p || !q || !r)
    {
        ret = ERROR_CODE;
        goto exit;
    }

    // do something

  exit:
    free(p);
    free(q);
    free(r);
    return ret;
}

最终可能性:如果您确实想在malloc失败时退出程序,请考虑使用malloptM_CHECK_ACTION选项。 这使得malloc()错误得到检查,并调用abort() ,可能会显示一条有用的消息。

从手册页:

名称

mallopt设置内存分配参数

概要

  #include <malloc.h> int mallopt(int param, int value); 

描述

mallopt()函数调整用于控制内存分配函数行为的参数(请参阅malloc(3) )。 param参数指定要修改的参数,而value指定该参数的新值。

可以为param指定以下值:

M_CHECK_ACTION

设置此参数可控制在检测到各种编程错误(例如,释放同一指针两次)时glibc的响应方式。 分配给此参数的值的3个最低有效位(2、1,和0)确定glibc行为,如下所示:

位0 :如果该位置1,则在stderr上打印一条单行消息,其中提供有关错误的详细信息。 该消息以字符串"*** glibc detected ***"开头,然后是程序名称,在其中检测到错误的内存分配函数的名称,错误的简要说明以及内存地址,其中检测到错误。

位1 :如果设置了此位,则在打印由位0指定的任何错误消息之后,通过调用abort(3)终止程序。 在glibc的版本,因为2.4,如果位0也被置位,则,打印错误消息并中止之间,该程序还打印在上的状态的堆栈跟踪backtrace(3)并打印在风格进程的存储器映射/proc/[pid]/maps (请参阅proc(5) )。

位2 :(自glibc 2.4起)仅当还设置了位0时,此位才有效。 如果设置了此位,则描述错误的单行消息将简化为仅包含检测到错误的函数的名称以及错误的简短描述。

由于将NULL传递给free()是完全可以的,因此您可以在“直线”中分配所需的所有内容,一次性检查所有内容,然后在完成后释放所有内容,无论您是否拥有实际完成了任何工作:

char *p = malloc(BUFSIZ);
char *q = malloc(BUFSIZ);
char *r = malloc(BUFSIZ);
if (p && q && r) {
    /* Do some other work... */
}
free(p);
free(q);
free(r);

只要没有中间依赖项,即不具有多级依赖项的结构,此方法就起作用。 在执行此操作时,最好定义一个用于释放这种结构的函数,而不假定所有内存块均为非NULL

对于大量分配,我会花时间创建一个跟踪分配的内存管理器。 这样,无论功能是否成功,您都不必担心泄漏。

通常的想法是为malloc创建一个包装器,以记录成功的分配,然后根据请求释放它们。 要释放内存,只需将特殊大小传递给包装函数。 使用尺寸的0来释放内存,如果你知道你没有实际分配的将是适合0大小的块。 否则,您可能希望使用~0ULL作为免费请求大小。

这是一个简单的示例,每次释放之间最多允许100个分配。

#define FREE_ALL_MEM 0

void *getmem( size_t size )
{
    static void *blocks[100];
    static int count = 0;

    // special size is a request to free all memory blocks
    if ( size == FREE_ALL_MEM )
    {
        for ( int i = 0; i < count; i++ )
            free( blocks[i] );
        count = 0;
        return NULL;
    }

    // using a linked list of blocks would allow an unlimited number of blocks
    // or we could use an array that can be expanded with 'realloc'
    // but for this example, we have a fixed size array
    if ( count == 100 )
        return NULL;

    // allocate some memory, and save the pointer in the array
    void *result = malloc( size );
    if ( result )
        blocks[count++] = result;

    return result;
}

int foo( void )
{
    char *p, *q;

    if ( (p = getmem(BUFSIZ)) == NULL ) {
        return ERROR_CODE;
    }
    if ( (q = getmem(BUFSIZ)) == NULL ) {
        getmem( FREE_ALL_MEM );
        return ERROR_CODE;
    }

    /* Do some other work... */

    getmem( FREE_ALL_MEM );
    return SUCCESS_CODE;
}

这是习惯问题,但我更喜欢:

int returnFlag = FAILURE;

if ((p = malloc...) != NULL)
{
    if ((q = malloc..) != NULL)
    {
        // do some work
        returnFlag = SUCCESS; // success only if it is actually success

        free(q);
    }
    free(p);
}

return returnFlag; // all other variants are failure

如果您希望分配大量的项目,它可能会变得凌乱。 尝试避免“ goto”方法。 并不是因为过去的“ goto is bad”道德准则,而是因为这种方式确实可以掩盖疯狂和内存泄漏。

对于少量的malloc来说,这有点矫kill过正,但是您可以考虑使用以下方法:

void free_mem(void **ptrs, size_t len)
{
    for (size_t i = 0; i < len; ++i)
    {
        free(ptrs[i]);
        ptrs[i] = NULL;
    }
}

int foo(...)
{
    void *to_be_freed[N];
    int next_ptr = 0;
    for (size_t i = 0; i < N; ++i) to_be_freed[i] = NULL;

    p = malloc(..);
    if (!p)
    {
        free_mem(to_be_freed,N);
        return ERROR_CODE;
    }
    to_be_freed[next_ptr++] = p;

    // Wash, rinse, repeat, with other mallocs
    free_mem(to_be_freed,N)
    return SUCCESS;
}

实际上,您可以使用跟踪此内容的东西包装malloc。 将数组和数组大小放入结构中,然后将其传递给所需的分配大小。

我认为第一个答案是最通用的,因为它可以用于除malloc引起的错误以外的其他错误。 但是我会删除gotos并像这样使用一次单次while循环。

int foo()
{
  char *p = NULL;
  char *q = NULL;
  int ret = 0;
  do {
    if (NULL == (p = malloc(BUFSIZ)))
    {
      ret = ERROR_CODE;
      break;
    }

    // possibly do something here

    if (NULL == (q = malloc(BUFSIZ)))
    {
      ret = ERROR_CODE;
      break;
    }

    // insert similar repetitions

    // hopefully do something here
  } while(0);
  free (p);
  free (q);
  return ret;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM