user295190
user295190

Reputation:

C: Nested Ifs or Gotos

What is the best way to manage resources for a C program. Should I use a nested if structure or should I use goto statements?

I am aware there is a lot of taboo about goto statements. However, I think it is justified for local resource clean up. I have supplied two samples. One compares a nested if structure and another uses goto statements. I personally find the goto statements make the code easier to read. For those who might argue that the nested if prompt better structure, imagine if the datatype was something other than a char*, like a Windows handle. I feel that the nested if structure would get out of hand with a series of CreateFile functions or any other function that takes large quantities of parameters.

This article demonstrates that local goto statements create RAII for C code. The code is neat an easy to follow. Imagine that as a series of nested if statements.

I understand that goto is taboo in many other languages because their exists other control mechanisms like try/catch etc, however, in C it seems appropriate.

#include <stdlib.h>

#define STRING_MAX 10

void gotoExample()
{
    char *string1, *string2, *string3, *string4, *string5;

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string1;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string2;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string3;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string4;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto gotoExample_string5;

    //important code goes here

gotoExample_string5:
    free(string4);
gotoExample_string4:
    free(string3);
gotoExample_string3:
    free(string2);
gotoExample_string2:
    free(string1);
gotoExample_string1:
}

void nestedIfExample()
{
    char *string1, *string2, *string3, *string4, *string5;

    if (string1 = (char*) calloc(STRING_MAX, sizeof(char))) 
    {
        if (string2 = (char*) calloc(STRING_MAX, sizeof(char)))
        {
            if (string3 = (char*) calloc(STRING_MAX, sizeof(char)))
            {
                if (string4 = (char*) calloc(STRING_MAX, sizeof(char)))
                {
                    if (string5 = (char*) calloc(STRING_MAX, sizeof(char)))
                    {
                        //important code here
                        free(string5);
                    }
                    free(string4);
                }
                free(string3);
            }
            free(string2);
        }
        free(string1);
    }
}


int main(int argc, char* argv[])
{
    nestedIfExample();
    gotoExample();
    return 0;
}

I would also like to quote Linus Torvalds on goto statements inside the Linux Kernel.

And sometimes structure is bad, and gets into the way, and using a "goto" is just much clearer.

For example, it is quite common to have conditionals THAT DO NOT NEST.

In which case you have two possibilities

  • use goto, and be happy, since it doesn't enforce nesting

    This makes the code more readable, since the code just does what the algorithm says it should do.

  • duplicate the code, and rewrite it in a nesting form so that you can
    use the structured jumps.

    This often makes the code much LESS readable, harder to maintain, and bigger.

The Pascal language is a prime example of the latter problem. Because it doesn't have a "break" statement, loops in (traditional) Pascal end up often looking like total shit, because you have to add totally arbitrary logic to say "I'm done now".

Is goto acceptable for resource management? Should I use nested if statements or is there a better way?

Update: Examples of Good Gotos In C

Upvotes: 9

Views: 5864

Answers (11)

hopia
hopia

Reputation: 5006

For me, I prefer this style of goto error handling. Taking Nick D's snippet one step further, it uses one general goto label for error handling.

void gotoExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = NULL;

    if ( !(string1 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string2 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string3 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string4 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;
    if ( !(string5 = (char*) calloc(STRING_MAX, sizeof(char))) )
        goto HANDLE_ERROR;

    //important code goes here


HANDLE_ERROR:
  if (string5)
    free(string5);

  if (string4)
    free(string4);

  if (string3)
    free(string3);

  if (string2)
    free(string2);

  if (string1)
    free(string1);

}

Upvotes: 0

Nick Dandoulakis
Nick Dandoulakis

Reputation: 43110

If by using goto you can avoid writing complex code, then use goto.

Your example could also be written like this (no gotos):

void anotherExample()
{
    char *string1, *string2, *string3, *string4, *string5;
    string1 = string2 = string3 = string4 = string5 = 0;
    if ((string1 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string2 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string3 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string4 = (char*) calloc(STRING_MAX, sizeof(char)))
     && (string5 = (char*) calloc(STRING_MAX, sizeof(char))))
    {
       //important code here
    }

  free(string1);
  free(string2);
  free(string3);
  free(string4);
  free(string5);
}

Upvotes: 11

DigitalRoss
DigitalRoss

Reputation: 146053

Always use one goto in each program to annoy the purists


That's my philosophy.

Seriously, on some occasions a goto is reasonable, especially if it just does something obvious like jump to common return code at the bottom of a function.

Upvotes: 6

Pete Kirkham
Pete Kirkham

Reputation: 49311

One very big difference between the example in the article you link to and the code you're posting is that your gotos labels are <functionName>_<number> and their goto labels are cleanup_<thing_to_cleanup>.

You're be using goto line_1324 next, and the code will get edited so the line_1234 label is on line 47823 ...

Use it like the example, and be very careful to write code to be read.

Upvotes: 2

Jerry Coffin
Jerry Coffin

Reputation: 490108

I'd structure the code differently than either one. Unless I had some outstanding reason to do otherwise, I'd probably write the code something like this:

char *strings[5] = {NULL};

int all_good = 1;

for (i=0; i<5 && all_good; i++) {
    strings[i] = malloc(STRING_MAX);
    all_good &= strings[i] != NULL;
}

if (all_good)
    important_code();

for (int i=0; i<5; i++)
    free(strings[i]);

Upvotes: 2

NealB
NealB

Reputation: 16928

No doubt about it Dijkstra was a formidable personality in the programming world. His Goto Considered Harmful paper was way overblown. Yes GoTo may be used indiscriminately and can be harmfull but many think an outright ban on GoTo is not warranted. Knuth provided a very well reasoned rebuttal to Dijkstra in: Structured Programming with GO TOs

Read Knuth's paper, you will find that your GoTo pattern is one of the good uses for GoTo.

BTW, Dijkstra is very quotable for a number of other things too. How about:

  • Object-oriented programming is an exceptionally bad idea which could only have originated in California.

Dijkstra was a great mathematician and made huge contributions to computer science. However, I don't think he had to deal with, or was interested in, the day to day type stuff that 99.99 percent of our programs do.

Use GoTo only with reason and structure. Use them rarely. But do use them.

Upvotes: 11

Charles Salvia
Charles Salvia

Reputation: 53289

In C, goto is often the only way to approximate cleanup code like C++ destructors or Java finally clauses. Since it's really the best tool you have for that purpose, I say use it. Yeah, it's easy to abuse, but so are a lot of programming constructs. For example, most Java programmers wouldn't hesitate to throw exceptions, but exceptions are also easy to abuse if they're used for something other than error reporting, such as flow control. But if you're using goto explicitly for the purpose of avoiding cleanup-code duplication, then it's safe to say you're probably not abusing it.

You can find many perfectly reasonable uses of goto in the Linux kernel, for example.

Upvotes: 1

ninjalj
ninjalj

Reputation: 43688

Cleanup using goto has the advantage that it's less error-prone. Having to free each and every resource allocated on each and every return point can lead to someone someday missing some cleanup when doing maintenance work.

That said, I'll quote Knuth's "Structured Programming with goto Statements":

I argue for the elimination of go to's in certain cases, and for their introduction in others.

and Knuth's quote of Dijkstra in that same paper:

"Please don't fall into the trap of believing that I am terribly dogmatical about [the go to statement]. I have the uncomfortable feeling that others are making a religion out of it, as if the conceptual problems of programming could be solved by a single trick, by a simple form of coding discipline!" [29].

Upvotes: 10

Mirek Kratochvil
Mirek Kratochvil

Reputation: 752

If you know what you're doing, and the resulting code looks cleaner and more readable (I bet it does), then there's absolutely no problem with using goto's. Especially the 'graceful initialization failure recovery' example you showed is widely used.

BTW, when writing structured code that initializes 100 things, you'd need 100 levels of indentation...which is plain ugly.

Upvotes: 1

Armen Tsirunyan
Armen Tsirunyan

Reputation: 132994

From two of your alternatives, goto is naturally better and nicer. But there's a third and better alternative: Use recursion!

Upvotes: 2

cdhowie
cdhowie

Reputation: 169008

Personally I have used goto in this manner in the past. People hate it because it reminds them of the spaghetti code they used to write/maintain, or because someone who wrote/maintaned such code beat the concept that gotos are evil into them.

You probably could write something decent without goto, sure. But it's not going to do any harm in this kind of circumstance.

Upvotes: 3

Related Questions