如何正确处理C语言中的malloc故障,特别是在有多个malloc的情况下?

时间:2022-09-06 16:19:15

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". 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失败了,所以我在第二个“错误处理程序”中使用了free(p)。但是如果有更多的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? (maybe using some goto?)

我知道在c++中有像RAII和exception safe之类的东西,但是一般来说,在C中处理malloc故障的正确方法是什么?(也许使用goto ?)

6 个解决方案

#1


31  

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.

注意,释放空值被定义为不操作。

This avoids n levels of indent for n variables. You can clean up filehandles etc. similarly (though you'll have to put a condition around the close()).

这避免了n个变量的n阶缩进。您可以同样地清理文件句柄等(尽管您必须在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. This makes malloc() faults get checked, and calls abort(), possibly printing a helpful message.

最后的可能性:如果你真的想退出malloc的程序,可以考虑使用mallopt的M_CHECK_ACTION选项。这使得malloc()错误被检查,并调用abort(),可能会打印出有用的消息。

From the man page:

从手册页:

NAME

的名字

mallopt - set memory allocation parameters

设置内存分配参数。

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)). The param argument specifies the parameter to be modified, and value specifies the new value for that parameter.

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

The following values can be specified for param:

可以为param指定以下值:

M_CHECK_ACTION

M_CHECK_ACTION

Setting this parameter controls how glibc responds when various kinds of programming errors are detected (e.g., freeing the same pointer twice). The 3 least significant bits (2, 1, and 0) of the value assigned to this parameter determine the glibc behavior, as follows:

设置此参数可以控制在检测到各种编程错误时glibc的响应(例如,释放同一个指针两次)。分配给该参数的值的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. 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.

位0:如果设置了这个位,则在stderr上打印一条单行消息,提供有关错误的详细信息。消息以字符串“*** glibc检测***”开始,然后是程序名、内存分配函数的名称、错误被检测到的、错误的简要描述,以及检测到错误的内存地址。

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). 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)).

位1:如果设置了这个位,那么,在打印了由位0指定的任何错误消息之后,程序将通过调用abort(3)终止。在glibc版本中,如果还设置了0,那么在打印错误消息和中止过程之间,程序还会以回溯(3)的方式打印堆栈跟踪,并以/proc/[pid]/映射的方式打印进程的内存映射(见proc(5))。

Bit 2: (since glibc 2.4) This bit has an effect only if bit 0 is also set. 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.

2:(因为glibc 2.4)这一点一点有影响只有0也设置。如果这一点,然后一行消息描述错误的是简化为只包含函数的名称的错误检测和错误的简短描述。

#2


23  

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, i.e. 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.

只要没有中间依赖项,就可以工作,也就是说,您没有具有多层依赖关系的结构。当您这样做时,最好定义一个函数来释放这样的结构,而不必假设所有的内存块都是非空的。

#3


3  

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. 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. Otherwise, you may want to use ~0ULL as the request-to-free size.

一般的想法是为malloc创建一个包装器,记录成功的分配,然后根据请求释放它们。要释放内存,只需将一个特殊的大小传递给包装器函数。如果您知道您的实际分配将不会是0个大小块,那么使用0到空闲内存的大小是合适的。否则,您可能希望使用~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;
}

#4


2  

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

#5


2  

IF you are expecting to allocate a large number of items, it Can get messy. Try to avoid the 'goto' approach. Not because of the old 'goto is bad' ethic, but because that way really can lie madness and memory leaks.

如果您希望分配大量的项目,它可能会变得混乱。尽量避免使用“goto”方法。不是因为旧的“goto是坏的”道德,而是因为那样真的可以撒谎和内存泄漏。

It's a little overkill for small numbers of malloc, but you can consider something like this approach:

对于少量的malloc来说,这有点过分了,但是你可以考虑这样的方法:

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. Put the array and array size in a structure and pass that in with the desired allocation size.

在现实中,你可能会把malloc和跟踪它的东西打包。将数组和数组大小放在一个结构中,并将其传递到所需的分配大小。

#6


2  

I think the first answer is the most general purpose as it can be used for errors other than those caused by malloc. However I would remove the gotos and use a single pass while loop like so.

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

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

#1


31  

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.

注意,释放空值被定义为不操作。

This avoids n levels of indent for n variables. You can clean up filehandles etc. similarly (though you'll have to put a condition around the close()).

这避免了n个变量的n阶缩进。您可以同样地清理文件句柄等(尽管您必须在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. This makes malloc() faults get checked, and calls abort(), possibly printing a helpful message.

最后的可能性:如果你真的想退出malloc的程序,可以考虑使用mallopt的M_CHECK_ACTION选项。这使得malloc()错误被检查,并调用abort(),可能会打印出有用的消息。

From the man page:

从手册页:

NAME

的名字

mallopt - set memory allocation parameters

设置内存分配参数。

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)). The param argument specifies the parameter to be modified, and value specifies the new value for that parameter.

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

The following values can be specified for param:

可以为param指定以下值:

M_CHECK_ACTION

M_CHECK_ACTION

Setting this parameter controls how glibc responds when various kinds of programming errors are detected (e.g., freeing the same pointer twice). The 3 least significant bits (2, 1, and 0) of the value assigned to this parameter determine the glibc behavior, as follows:

设置此参数可以控制在检测到各种编程错误时glibc的响应(例如,释放同一个指针两次)。分配给该参数的值的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. 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.

位0:如果设置了这个位,则在stderr上打印一条单行消息,提供有关错误的详细信息。消息以字符串“*** glibc检测***”开始,然后是程序名、内存分配函数的名称、错误被检测到的、错误的简要描述,以及检测到错误的内存地址。

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). 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)).

位1:如果设置了这个位,那么,在打印了由位0指定的任何错误消息之后,程序将通过调用abort(3)终止。在glibc版本中,如果还设置了0,那么在打印错误消息和中止过程之间,程序还会以回溯(3)的方式打印堆栈跟踪,并以/proc/[pid]/映射的方式打印进程的内存映射(见proc(5))。

Bit 2: (since glibc 2.4) This bit has an effect only if bit 0 is also set. 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.

2:(因为glibc 2.4)这一点一点有影响只有0也设置。如果这一点,然后一行消息描述错误的是简化为只包含函数的名称的错误检测和错误的简短描述。

#2


23  

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, i.e. 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.

只要没有中间依赖项,就可以工作,也就是说,您没有具有多层依赖关系的结构。当您这样做时,最好定义一个函数来释放这样的结构,而不必假设所有的内存块都是非空的。

#3


3  

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. 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. Otherwise, you may want to use ~0ULL as the request-to-free size.

一般的想法是为malloc创建一个包装器,记录成功的分配,然后根据请求释放它们。要释放内存,只需将一个特殊的大小传递给包装器函数。如果您知道您的实际分配将不会是0个大小块,那么使用0到空闲内存的大小是合适的。否则,您可能希望使用~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;
}

#4


2  

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

#5


2  

IF you are expecting to allocate a large number of items, it Can get messy. Try to avoid the 'goto' approach. Not because of the old 'goto is bad' ethic, but because that way really can lie madness and memory leaks.

如果您希望分配大量的项目,它可能会变得混乱。尽量避免使用“goto”方法。不是因为旧的“goto是坏的”道德,而是因为那样真的可以撒谎和内存泄漏。

It's a little overkill for small numbers of malloc, but you can consider something like this approach:

对于少量的malloc来说,这有点过分了,但是你可以考虑这样的方法:

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. Put the array and array size in a structure and pass that in with the desired allocation size.

在现实中,你可能会把malloc和跟踪它的东西打包。将数组和数组大小放在一个结构中,并将其传递到所需的分配大小。

#6


2  

I think the first answer is the most general purpose as it can be used for errors other than those caused by malloc. However I would remove the gotos and use a single pass while loop like so.

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

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