Reputation:
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
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
Reputation: 43110
If by using goto
you can avoid writing complex code, then use goto
.
Your example could also be written like this (no goto
s):
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
Reputation: 146053
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
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
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
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:
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
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
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
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
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
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