The linux style guide gives specific reasons to use goto
's which are in line with your example:
https://www.kernel.org/doc/Documentation/process/coding-style.rst
The rationale for using gotos is:
- unconditional statements are easier to understand and follow
- nesting is reduced
- errors by not updating individual exit points when making
modifications are prevented
- saves the compiler work to optimize redundant code away ;)
Disclaimer I'm not supposed to share my work. The examples here are a bit contrived so bear please bear with me.
This is good for memory management. I recently worked on code that had dynamically allocated memory (for example a char *
returned by a function). A function that looks at a path and ascertains whether the path is valid by parsing the tokens of the path:
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
free(var1);
free(var2);
...
free(varN);
return 1;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
free(var1);
free(var2);
...
free(varN);
return 1;
} else {
free(var1);
free(var2);
...
free(varN);
return 0;
}
}
token = strtok(NULL,delim);
}
free(var1);
free(var2);
...
free(varN);
return 1;
Now to me, the following code is much nicer and easier to maintain if you need to add a varNplus1
:
int retval = 1;
tmp_string = strdup(string);
token = strtok(tmp_string,delim);
while( token != NULL ){
...
some statements, some involving dynamically allocated memory
...
if ( check_this() ){
retval = 1;
goto out_free;
}
...
some more stuff
...
if(something()){
if ( check_that() ){
retval = 1;
goto out_free;
} else {
retval = 0;
goto out_free;
}
}
token = strtok(NULL,delim);
}
out_free:
free(var1);
free(var2);
...
free(varN);
return retval;
Now the code had all sorts of other problems with it, namely that N was somewhere above 10, and the function was over 450 lines, with 10 levels of nestedness in some places.
But I offered my supervisor to refactor it, which I did and now it's a bunch of functions that are all short, and they all have the linux style
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 == NULL ){
retval = 0;
goto out;
}
if( isValid(var1) ){
retval = some_function(var1);
goto out_free;
}
if( isGood(var1) ){
retval = 0;
goto out_free;
}
out_free:
free(var1);
out:
return retval;
}
If we consider the equivalent without goto
s:
int function(const char * param)
{
int retval = 1;
char * var1 = fcn_that_returns_dynamically_allocated_string(param);
if( var1 != NULL ){
if( isValid(var1) ){
retval = some_function(var1);
} else {
if( isGood(var1) ){
retval = 0;
}
}
free(var1);
} else {
retval = 0;
}
return retval;
}
To me, in the first case, it is obvious to me that if the first function returns NULL
, we're outta here and we're returning 0
. In the second case, I have to scroll down to see that the if contains the whole function. Granted the first one indicates this to me stylistically (the name "out
") and the second one does it syntactically. The first one is still more obvious.
Also, I greatly prefer having free()
statements at the end of a function. That's partly because, in my experience, free()
statements in the middle of functions smell bad and indicate to me that I should create a subroutine. In this case, I created var1
in my function and could not free()
it in a subroutine, but that's why the goto out_free
, goto out style is so practical.
I think programmers need to be brought up believing that goto
's are evil. Then, when they're mature enough, they should browse the Linux source code and read the linux style guide.
I should add that I use this style very consistently, every function has an int retval
, an out_free
label and an out label. Because of stylistic consistency, readability is improved.
Bonus: Breaks and continues
Say you have a while loop
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
if( functionC(var1, var2){
count++
continue;
}
...
a bunch of statements
...
count++;
free(var1);
free(var2);
}
There are other things wrong with this code, but one thing is the continue statement. I would like to rewrite the whole thing, but I was tasked with modifying it in a small way. It would have taken me days to refactor it in a way that satisfied me, but the actual change was about a half day's work. The problem is that even if we 'continue
' we still need to free var1
and var2
. I had to add a var3
, and it made me want to puke to have to mirror the free() statements.
I was a relatively new intern at the time, but I had been looking at the linux source code for fun a while back, so I asked my supervisor if I could use a goto statement. He said yes, and I did this:
char *var1, *var2;
char line[MAX_LINE_LENGTH];
while( sscanf(line,... ){
var1 = functionA(line,count);
var2 = functionB(line,count);
var3 = newFunction(line,count);
if( functionC(var1, var2){
goto next;
}
...
a bunch of statements
...
next:
count++;
free(var1);
free(var2);
}
I think continues are OK at best but to me they're like a goto with an invisible label. The same goes for breaks. I would still prefer continue or break unless, as was the case here, it forces you to mirror modifications in multiple places.
And I should also add that this use of goto next;
and the next:
label are unsatisfactory to me. They are merely better than mirroring the free()
's and the count++
statements.
goto
's are almost always wrong, but one must know when they are good to use.
One thing that I didn't discuss is error handling which has been covered by other answers.
Performance
One can look at the implementation of strtok() http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
#include <stddef.h>
#include <string.h>
char *
strtok(s, delim)
register char *s;
register const char *delim;
{
register char *spanp;
register int c, sc;
char *tok;
static char *last;
if (s == NULL && (s = last) == NULL)
return (NULL);
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0;) {
if (c == sc)
goto cont;
}
if (c == 0) { /* no non-delimiter characters */
last = NULL;
return (NULL);
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;) {
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0)
s = NULL;
else
s[-1] = 0;
last = s;
return (tok);
}
} while (sc != 0);
}
/* NOTREACHED */
}
Please correct me if I'm wrong, but I believe that the cont:
label and the goto cont;
statement are there for performance (they surely don't make the code more readable). They could be replaced with readable code by doing
while( isDelim(*s++,delim));
to skip delimiters. But to be as fast as possible and avoid unnecessary function calls, they do it this way.
I read the paper by Dijkstra and I find it quite esoteric.
google "dijkstra goto statement considered harmful" because I don't have enough reputation to post more than 2 links.
I've seen it quoted as a reason not to use goto's and reading it has not changed anything as far as my uses of goto's are concered.
Addendum:
I've come up with a neat rule while thinking about all this about continues and breaks.
- If in a while loop, you have a continue, then the body of the while
loop should be a function and the continue be a return statement.
- If in a while loop, you have a break statement, then the while loop
itself should be a function and the break should become a return
statement.
- If you have both, then something might be wrong.
It's not always possible due to scope issues but I've found that doing this make it much easier to reason about my code. I had noticed that whenever a while loop had a break or a continue it gave me a bad feeling.
goto hell;