简体   繁体   English

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

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

Suppose this is a part of my code: 假设这是我的代码的一部分:

 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);  
 }

Since it's possible that the first malloc is successful but the second one fails, I use free(p) in the second "error handler". 由于第一个malloc可能成功而第二个malloc失败,所以我在第二个“错误处理程序”中使用free(p) But what if there are more malloc 's and what if I want to modify the code (adjusting their orders, adding or deleting some malloc )? 但是,如果还有更多的malloc呢?如果我想修改代码(调整它们的顺序,添加或删除一些malloc )怎么办?

I know in C++ there are things like RAII and exception safe, etc. But in general, what is the correct way to handle malloc failure in C? 我知道在C ++中有诸如RAII和异常安全之类的东西。但是总的来说,在C中处理malloc失败的正确方法是什么? (maybe using some goto ?) (也许使用一些goto吗?)

Your code is fine, but for lots of variables, I'd prefer: 您的代码很好,但是对于许多变量,我希望:

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;
}

Note that freeing NULL is defined as a no-op. 请注意,将NULL释放定义为无操作。

This avoids n levels of indent for n variables. 这避免了n n变量的n个缩进级别。 You can clean up filehandles etc. similarly (though you'll have to put a condition around the close() ). 您可以类似地清理文件句柄等(尽管您必须在close()周围放置一个条件)。

Now, if you know you can allocate them all at once, then dasblinkenlight has a good answer, but here's another way: 现在,如果您知道可以一次分配它们,那么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;
}

Final possibility: if you actually want to exit the program on malloc fail, consider using mallopt 's M_CHECK_ACTION option. 最终可能性:如果您确实想在malloc失败时退出程序,请考虑使用malloptM_CHECK_ACTION选项。 This makes malloc() faults get checked, and calls abort() , possibly printing a helpful message. 这使得malloc()错误得到检查,并调用abort() ,可能会显示一条有用的消息。

From the man page: 从手册页:

NAME 名称

mallopt - set memory allocation parameters mallopt设置内存分配参数

SYNOPSIS 概要

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

DESCRIPTION 描述

The mallopt() function adjusts parameters that control the behavior of the memory-allocation functions (see malloc(3) ). mallopt()函数调整用于控制内存分配函数行为的参数(请参阅malloc(3) )。 The param argument specifies the parameter to be modified, and value specifies the new value for that parameter. param参数指定要修改的参数,而value指定该参数的新值。

The following values can be specified for param : 可以为param指定以下值:

M_CHECK_ACTION

Setting this parameter controls how glibc responds when various kinds of programming errors are detected (eg, freeing the same pointer twice). 设置此参数可控制在检测到各种编程错误(例如,释放同一指针两次)时glibc的响应方式。 The 3 least significant bits (2, 1, and 0) of the value assigned to this parameter determine the glibc behavior, as follows: 分配给此参数的值的3个最低有效位(2、1,和0)确定glibc行为,如下所示:

Bit 0 : If this bit is set, then print a one-line message on stderr that provides details about the error. 位0 :如果该位置1,则在stderr上打印一条单行消息,其中提供有关错误的详细信息。 The message starts with the string "*** glibc detected ***" , followed by the program name, the name of the memory-allocation function in which the error was detected, a brief description of the error, and the memory address where the error was detected. 该消息以字符串"*** glibc detected ***"开头,然后是程序名称,在其中检测到错误的内存分配函数的名称,错误的简要说明以及内存地址,其中检测到错误。

Bit 1 : If this bit is set, then, after printing any error message specified by bit 0, the program is terminated by calling abort(3) . 位1 :如果设置了此位,则在打印由位0指定的任何错误消息之后,通过调用abort(3)终止程序。 In glibc versions since 2.4, if bit 0 is also set, then, between printing the error message and aborting, the program also prints a stack trace in the manner of backtrace(3) , and prints the process's memory mapping in the style of /proc/[pid]/maps (see proc(5) ). 在glibc的版本,因为2.4,如果位0也被置位,则,打印错误消息并中止之间,该程序还打印在上的状态的堆栈跟踪backtrace(3)并打印在风格进程的存储器映射/proc/[pid]/maps (请参阅proc(5) )。

Bit 2 : (since glibc 2.4) This bit has an effect only if bit 0 is also set. 位2 :(自glibc 2.4起)仅当还设置了位0时,此位才有效。 If this bit is set, then the one-line message describing the error is simplified to contain just the name of the function where the error was detected and the brief description of the error. 如果设置了此位,则描述错误的单行消息将简化为仅包含检测到错误的函数的名称以及错误的简短描述。

Since it is perfectly OK to pass NULL to free() , you could allocate everything that you need in a "straight line", check everything in a single shot, and then free everything once you are done, regardless of whether or not you have actually done any work: 由于将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);

This works as long as there are no intermediate dependencies, ie you do not have structures with multi-level dependencies. 只要没有中间依赖项,即不具有多级依赖项的结构,此方法就起作用。 When you do, it is a good idea to define a function for freeing such a structure, without assuming that all memory blocks are non- NULL . 在执行此操作时,最好定义一个用于释放这种结构的函数,而不假定所有内存块均为非NULL

For large numbers of allocations, I would invest the time in creating a memory manager that keeps track of the allocations. 对于大量分配,我会花时间创建一个跟踪分配的内存管理器。 That way, you never have to worry about leaks, regardless of whether or not the function succeeds. 这样,无论功能是否成功,您都不必担心泄漏。

The general idea is to create a wrapper for malloc that records successful allocations, and then frees them on request. 通常的想法是为malloc创建一个包装器,以记录成功的分配,然后根据请求释放它们。 To free memory, you simply pass a special size to the wrapper function. 要释放内存,只需将特殊大小传递给包装函数。 Using a size of 0 to free memory is appropriate if you know that none of your actual allocations will be for 0 sized blocks. 使用尺寸的0来释放内存,如果你知道你没有实际分配的将是适合0大小的块。 Otherwise, you may want to use ~0ULL as the request-to-free size. 否则,您可能希望使用~0ULL作为免费请求大小。

Here's a simple example that allows up to 100 allocations between frees. 这是一个简单的示例,每次释放之间最多允许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;
}

it is matter of habit, but I prefer: 这是习惯问题,但我更喜欢:

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

IF you are expecting to allocate a large number of items, it Can get messy. 如果您希望分配大量的项目,它可能会变得凌乱。 Try to avoid the 'goto' approach. 尝试避免“ goto”方法。 Not because of the old 'goto is bad' ethic, but because that way really can lie madness and memory leaks. 并不是因为过去的“ goto is bad”道德准则,而是因为这种方式确实可以掩盖疯狂和内存泄漏。

It's a little overkill for small numbers of malloc, but you can consider something like this approach: 对于少量的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;
}

In reality, you can probably wrap malloc with something which tracks this. 实际上,您可以使用跟踪此内容的东西包装malloc。 Put the array and array size in a structure and pass that in with the desired allocation size. 将数组和数组大小放入结构中,然后将其传递给所需的分配大小。

I think the first answer is the most general purpose as it can be used for errors other than those caused by malloc. 我认为第一个答案是最通用的,因为它可以用于除malloc引起的错误以外的其他错误。 However I would remove the gotos and use a single pass while loop like so. 但是我会删除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