views:

306

answers:

6

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.

+20  A: 

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: http://stackoverflow.com/questions/788903/valid-use-of-goto-for-error-management-in-c

karlphillip
+1 - while not popular in school, this is the most practical and is done all the time. put all of your free statements at a label just before leaving the function, check a status variable first if necessary, and use GOTO.
Brandon Horsley
How does that work? I have never heard about this technique. Can you illustrate it please?
bodacydo
There you go, it would be something like that.
karlphillip
The main caveat about this is that you need to know whether the variables actually were assigned. If the code jumped after the 'getbinder()' call, the variable `f` would not be initialized and would only coincidentally be zero, so you could easily end up freeing garbage, which is a recipe for trouble. So, you have to make sure that any allocated resources are initialized to zero at the top of the function. That's not saying the technique is wrong; pragmatically, it is quite effective. You do have to be a bit careful, though. If C++ is an option, destructors can clean up automatically.
Jonathan Leffler
That's how you implement `exception handling` in C.
Alexander Pogrebnyak
The thing to be really, really careful about when using goto is that you need to make sure you don't jump over something important and, say, leave a file open or leak memory.
Mike DeSimone
@Mike DeSimone: If you consolidate all of the cleanup code at the end, then you're *much* less likely to accidentally leave a file open or leak memory than if you had scattered (and duplicated) cleanup code throughout the rest of your function. And often much of that cleanup code could be shared with the successful code path, so it's more likely to be exercised and scrutinized.
jamesdlin
@Jonathan Leffler: The solution to that is to simply have multiple labels, one for each stage of initialization. The labels are in reverse order from the initializations. If an initialization fails, it jumps to the corresponding label at the end, skipping over cleanup for later initializations and falling through to clean up for earlier initializations. Used to use this trick all the time back when I did C.
siride
I suggest that you should use a stack to accumulate the resources, so you need just one labeled loop to jump to.
Cirno de Bergerac
@siride: yes - that also works. It requires care, but error handling generally does require care. The key point is to be aware of whether variables reliably have values set, making sure you don't free garbage (or close unopen file descriptors, or ...).
Jonathan Leffler
@siride and @Jonathan: I've updated the example (based on the OPs code sample) illustrating that.
caf
@Cirno de Bergerac: Then you additionally need to worry about possible failure when pushing items onto a stack, and if you have heterogeneous types of resources, you need to keep track of which items are what types and need to be freed with which functions. Doesn't sound like a win. IMO it's way easier just to initialize all the relevant variables at the beginning of the function and to use a single exit label.
jamesdlin
@jamesdlin: That works fine for `free()` cleanup, but not so much if you have to drop locks or reference counts as part of the cleanup.
caf
+3  A: 

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;
}
Mike DeSimone
Clarifying: scattering the local variable declarations around the block like in your code isn't standard C, but my first example meets the standard because each braced block has the declarations first.
Mike DeSimone
I think as of C99 this is now standard: http://en.wikipedia.org/wiki/C99#Design
Brandon Horsley
@Mike DeSimone: C has allowed it for 10 years now.
dreamlax
@dreamlax: C has allowed variables declared at the start of blocks for 30+ years; what is new in C99 is allowing variables declared part way through a block, like C++ has done for a long time (20+ years).
Jonathan Leffler
@Jonathan Leffler: that is what I was referring to.
dreamlax
+3  A: 

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

tommieb75
I agree, I tend to prefer maintaining a status variable, and having all paths through the function exit at the label, checking the status variable to free any memory allocations, or remove temp files, etc.
Brandon Horsley
@Brandon: Absolutely - that is one subtle thing that can trip up...and you go wtf happened... "oooh damn...I forgot it fell through the label...sighup" :D
tommieb75
A: 

If you want to do it without goto, here is an approach that scales well:

char *function(char *param)
{
    int status = 0;   // valid is 0, invalid is 1
    char *result = NULL;
    char *mem = NULL:
    struct binder* b = NULL;
    struct file* f = NULL:

    // check function parameter(s) for validity
    if (param == NULL)
    {
        status = 1;
    }

    if (status == 0)
    {
        mem = get_memory(100);  // first allocation

        if (!mem)
        {
            status = 1;
        }
    }

    if (status == 0)
    {
        b = get_binder('regular binder');  // second allocation

        if (!b)
        {
             status = 1;
        }
    }

    if (status == 0)
    {
        f = mk_file();  // third allocation

        if (!f)
        {
             status = 1;
        }
    }

    if (status == 0)
    {
        // do some useful work
        // assign value to result
    }

    // cleanup in reverse order
    free(f);
    free(b);
    free(mem);

    return result;
}
Amardeep
This is much more hideous than any goto could ever be.
R..
@R: Eye of the beholder. Some people consider `goto` a criteria to fail a code review.
Amardeep
That's because some people don't know how to think critically. No construct is always dangerous. Some constructs are more likely to cause problems than others. One ought to consider the costs and benefits, rather than blindly saying "never use X".
siride
While I prefer the goto solution, this is much cleaner than the naive approach. I don't think this is much harder to read than using goto, and a lot of people probably think it's easier. Still, I LOL'd at "some people don't know how to think critically." I tend to agree with that sentiment.
Steve S
One issue with this implementation -- if you're doing anything other than allocating memory (e.g. opening a file), you'll need to either make sure your cleanup functions will safely accept default-initialized values (the way free does nothing if NULL is passed in) or include extra tests before calling them. This isn't a big deal, but it's one reason why I like the goto version.
Steve S
+1  A: 

If your data structures are complicated/nested, a single goto might not suffice, in which case I suggest something like:

mystruct = malloc(sizeof *mystruct);
if (!mystruct) goto fail1;
mystruct->data = malloc(100);
if (!mystruct->data) goto fail2;
foo = malloc(sizeof *foo);
if (!foo) goto fail2;
...
return mystruct;
fail2:
free(mystruct->data);
fail1:
free(mystruct);

A real world example would be more complicated and could involve multiple levels of structure nesting, linked lists, etc. Note here that free(mystruct->data); cannot be called (because dereferencing an element of mystruct is invalid) if the first malloc failed.

R..
+2  A: 

You could also take the opposite approach and check for success:

struct binder* b = get_binder('regular binder');  // second allocation
if(b) {
   struct ... *c = ...
   if(c) {
      ...
   }
   free(b);
}
Janus Tøndering
and have a "results" variable for the final return.
Janus Tøndering