C Programming – Is This a Decent Use-Case for Goto?

cdesign-patternsgoto

I really hesitate to ask this, because I don't want to "solicit debate, arguments, polling, or extended discussion" but I'm new to C and want to gain more insight into common patterns used in the language.

I recently heard some distaste for the goto command, but I've also recently found a decent use-case for it.

Code like this:

error = function_that_could_fail_1();
if (!error) {
    error = function_that_could_fail_2();
    if (!error) {
        error = function_that_could_fail_3();
        ...to the n-th tab level!
    } else {
        // deal with error, clean up, and return error code
    }
} else {
    // deal with error, clean up, and return error code
}

If the clean-up part is all very similar, could be written a little prettier (my opinion?) like this:

error = function_that_could_fail_1();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
    goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
    goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code

Is this a common or acceptable use-case of goto in C? Is there a different/better way to do this?

Best Answer

The goto statement (and its corresponding labels) are a flow control primitive (along with conditional execution of a statement). By that, I mean that they are there to allow you to construct program flow control networks. You can think of them as modeling the arrows between the nodes of a flowchart.

Some of these can be optimized out immediately, where there is a direct linear flow (you just use a sequence of basic statements). Other patterns are best replaced with structured programming constructs where these are available; if it looks like a while loop, use a while loop, OK? The structured programming patterns are definitely at least potentially clearer of intent than a mess of goto statements.

Yet C does not include all possible structured programming constructs. (It's not clear to me that all relevant ones have been discovered yet; the rate of discovery is slow now, but I'd hesitate to jump to saying that all have been found.) Of the ones we know about, C definitely lacks the try/catch/finally structure (and exceptions too). It also lacks multi-level break-from-loop. These are the kinds of things which a goto can be used to implement. It's possible to use other schemes to do these too — we do know that C has a sufficient set of non-goto primitives — but these often involve creating flag variables and much more complex loop or guard conditions; increasing the entanglement of the control analysis with the data analysis makes the program harder to understand overall. It also makes it more difficult for the compiler to optimize and for the CPU to execute rapidly (most flow control constructs — and definitely goto — are very cheap).

Thus, while you shouldn't use goto unless needed, you should be aware that it exists and that it may be needed, and if you need it, you shouldn't feel too bad. An example of a case where it is needed is resource deallocation when a called function returns an error condition. (That is, try/finally.) It's possible to write that without goto but doing that can have downsides of its own, such as the problems of maintaining it. An example of the case:

int frobnicateTheThings() {
    char *workingBuffer = malloc(...);
    int i;

    for (i=0 ; i<numberOfThings ; i++) {
        if (giveMeThing(i, workingBuffer) != OK)
            goto error;
        if (processThing(workingBuffer) != OK)
            goto error;
        if (dispatchThing(i, workingBuffer) != OK)
            goto error;
    }

    free(workingBuffer);
    return OK;

  error:
    free(workingBuffer);
    return OOPS;
}

The code could be even shorter, but it's enough to demonstrate the point.

Related Topic