Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid long chain of free's (or deletes) after every error check in C?

Suppose I write my code very defensively and always check the return types from all the functions that I call.

So I go like:

char* function() {
    char* mem = get_memory(100);  // first allocation
    if (!mem) return NULL;

    struct binder* b = get_binder('regular binder');  // second allocation
    if (!b) {
        free(mem);
        return NULL;
    }

    struct file* f = mk_file();  // third allocation
    if (!f) {
        free(mem);
        free_binder(b);
        return NULL;
    }

    // ...
}

Notice how quickly free() things get out of control. If some of the function fails, I have to free every single allocation before. The code quickly becomes ugly and all I do is copy paste everything over. I become a copy/paste programmer, even worse, if someone adds a statement somewhere in between, he has to modify all the code below to call the free() for his addition.

How do experienced C programmers go about this problem? I can't figure anything out.

Thanks, Boda Cydo.

like image 501
bodacydo Avatar asked Jul 27 '10 00:07

bodacydo


3 Answers

You could define a new label that would free the resources and then you could GOTO it whenever your code fails.

char* function()
{
    char* retval = NULL;
    char* mem = get_memory(100);  // first allocation
    if (!mem)
        goto out_error;

    struct binder* b = get_binder('regular binder');  // second allocation
    if (!b)
        goto out_error_mem;

    struct file* f = mk_file();  // third allocation
    if (!f)
        goto out_error_b;

    /* ... Normal code path ... */
    retval = good_value;

  out_error_b:
    free_binder(b);
  out_error_mem:
    free(mem);
  out_error:
    return retval;
}

Error management with GOTO was already discussed here: Valid use of goto for error management in C?

like image 66
karlphillip Avatar answered Nov 10 '22 13:11

karlphillip


I know I'll get lynched for this, but I had a friend who said they used goto for that.

Then he told me that it wasn't enough in most cases and he now used setjmp()/longjmp(). Basically he reinvented C++'s exceptions but with much less elegance.

That said, since goto could work, you could refactor it into something that doesn't use goto, but the indentation will get out of hand fast:

char* function() {
    char* result = NULL;
    char* mem = get_memory(100);
    if(mem) {
        struct binder* b = get_binder('regular binder');
        if(b) {
            struct file* f = mk_file();
            if (f) {
                // ...
            }
            free(b);
        }
        free(mem);
    }
    return result;
}

BTW, scattering the local variable declarations around the block like that isn't standard C.

Now, if you realize that free(NULL); is defined by the C standard as a do-nothing, you can simplify the nesting some:

char* function() {
    char* result = NULL;

    char* mem = get_memory(100);
    struct binder* b = get_binder('regular binder');
    struct file* f = mk_file();

    if (mem && b && f) {
        // ...
    }

    free(f);
    free(b);
    free(mem);

    return result;
}
like image 6
Mike DeSimone Avatar answered Nov 10 '22 12:11

Mike DeSimone


While I admire your approach to defensive coding and that's a good thing. And every C Programmer should have that mentality, it can apply to other languages as well...

I have to say this is the one thing useful about GOTO, despite purists will say otherwise, that would be an equivalent of a finally block but there's one particular gotcha that I can see there...

karlphillip's code is nearly complete but .... suppose the function was done like this

    char* function() {
      struct file* f = mk_file();  // third allocation
      if (!f) goto release_resources;
      // DO WHATEVER YOU HAVE TO DO.... 
      return some_ptr;
   release_resources:
      free(mem);
      free_binder(b);
      return NULL;
    }

Be careful!!! This would depend on the design and the purpose of the function that you see fit, that put aside.. if you were to return from the function like that, you could end up falling through the release_resources label.... which could induce a subtle bug, all the references to the pointers on the heap are gone and could end up returning garbage... so make sure if you have the memory allocated and return it back, use the return keyword immediately before the label otherwise the memory could disappear... or create a memory leak....

like image 5
t0mm13b Avatar answered Nov 10 '22 11:11

t0mm13b