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

前端 未结 6 572
我在风中等你
我在风中等你 2021-02-01 02:49

Suppose this is a part of my code:

 int foo()
 {  
    char *p, *q ;
    if((p = malloc(BUFSIZ)) == NULL) {
        return ERROR_CODE;
    }
    if((q = malloc(B         


        
相关标签:
6条回答
  • 2021-02-01 03:23

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

    Now, if you know you can allocate them all at once, then dasblinkenlight has a good answer, but here's another way:

    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.

    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.

    The following values can be specified for param:

    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:

    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.

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

    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.

    0 讨论(0)
  • 2021-02-01 03:26

    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
    
    0 讨论(0)
  • 2021-02-01 03:30

    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.

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

    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.

    0 讨论(0)
  • 2021-02-01 03:31

    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.

    Here's a simple example that allows up to 100 allocations between frees.

    #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;
    }
    
    0 讨论(0)
  • 2021-02-01 03:42

    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:

    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.

    0 讨论(0)
  • 2021-02-01 03:42

    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.

    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;
    }
    
    0 讨论(0)
提交回复
热议问题