lp0 on fire

My personal blog

C89-Goto: Cleanup

In my previous article I explained how the goto statement can be used to emulate the switch-case control structure. For this last entry of the series, I'll demonstrate one of the few techniques that goto is still actively used for today in the embedded world: resource cleanup and as a barebone exception handling mechanism. For a change, this is something you can apply in production, and would heavily recommend you do so when it's appropiate!

The last but most important part of goto is that it can be done to handle cleanup elegantly in case an error occurs. For example, let's take this piece of code:

/* cleanup: if-return */

void *p1 = NULL;
void *p2 = NULL;
void *p3 = NULL;

p1 = malloc(16);

if (!p1)
{
    return;
}

/* use p1 here */

p2 = malloc(16);

if (!p2)
{
    if (p1) free(p1);
    return;
}

/* use p2 here */

p3 = malloc(16);

if (!p3)
{
    if (p2) free(p2);
    if (p1) free(p1);
    return;
}

/* use p3 here */

if (p3) free(p3);
if (p2) free(p2);
if (p1) free(p1);

The goal of this example is to allocate memory in steps, do something with it in between and allocate some more, until we reach the exit point where all memory needs to be freed. In between, we need to make sure that the memory is correctly allocated. If it isn't, clean up memory and terminate.

It looks like a real mess, but this is how I often encounter programmers handling cleanup because people do not know they could use goto for this:

/* cleanup: goto */

void *p1 = NULL;
void *p2 = NULL;
void *p3 = NULL;

p1 = malloc(16);
if (!p1) goto cleanup;

/* use p1 here */

p2 = malloc(16);
if (!p2) goto cleanup;

/* use p2 here */

p3 = malloc(16);
if (!p3) goto cleanup;

/* use p3 here */

cleanup:
if (p3) free(p3);
if (p2) free(p2);
if (p1) free(p1);

That looks much more concise, doesn't it?

This technique could also be used for handling errors that normally would throw an exception in other languages:

int Simulation_run(Foo* foo)
{
    int result = 0;

    if (!Foo_alloc(foo)) goto error_1;
    if (!Foo_ctor(foo)) goto error_2;

    result = Foo_runTest(foo);

    error_2:
    Foo_dtor(foo);

    error_1:
    Foo_dealloc(foo);

    return result;
}

Conclusion

Hopefully this series gave you some insight on how goto can be used to emulate control structures, what the code you write with normal control structures roughly gets translated to in assembly (without optimizations), and how you can do a very clean and concise cleanup of resources with goto.