It is common knowledge that usages of the goto statement are potentially dangerous in any structured programming language, as their abuse can quickly make your code unreadable. This is why this construction is seldom explained to people learning how to program and their use is strongly discouraged.
However, there are some situations in which it is very useful and, despite what some people might say, makes your code more readable. I had never used gotos before, but have experienced this recently while writing tmpfs.
One of the situations where a goto is useful is when you need break from within a set of nested loops. This is the most cited example of "correct" usage of this statement, but personally, I don't like it. FWIW some languages, such as Perl, provide a nice break statement to do exactly this.
Another scenario where goto is useful is what I'm using it for now: to control all exit conditions from a function. Instead of placing return statements all around inside them (after each error check, for example), I put a goto that jumps to the end of the function; that block is responsible for checking postconditions, doing any necessary cleanup and return the correct error code.
Here is an outline of such a function:
int
foo(...)
{
int error;/* Check function preconditions here using assert(3). */
if (error condition one) {
error = EFOO;
goto out;
}if (error condition two) {
error = EBAR;
goto out;
}/* Code executed when there are no errors. */
error = 0;out:
/* Release unneeded resources here. *//* Check function postconditions here using assert(3). */
return error;
}
As you can see, if I wanted to avoid using gotos, I'd have to duplicate a lot of code inside each error condition check. Using this structure, all exit-related stuff is kept in just one place; specially postconditions (remember that they must be fulfilled whichever action the function did). And the function is far more readable (at least to my eyes).
If you have any other example of nice goto usages, or if you see another way to do what I described above, please share! ;-)
23 comments:
[Originally posted at 2005-08-08 12:31 am UTC]
In C# you can do:
try {
// Check conditions and throw exception if not met
// Execute code
} catch {
// Print error message or simply do nothing
} finally {
// Cleanup
}
And that's why I like C#.
[Originally posted at 2005-08-07 11:42 pm UTC]
There's actually nothing wrong in using goto the way you are using (Think of them as poor man's exception handlers). Acutally, I would even argue that this a better way of doing than using functions or some other mechanism. There's nothing evil about gotos, it's how use them is more important. For that matter, every if, while, ... statements have a goto in them behind the scenes.
See http://kerneltrap.org/node/553
--
Pradeep Padala
http://ppadala.blogspot.com
[Originally posted at 2005-08-08 08:30 am UTC]
Yeah, and that's one of the reasons why I like C++ too. Hmm... C++ in kernel space, dunno if that could be easy/possible to do.
[Originally posted at 2005-08-08 02:56 pm UTC]
"Yeah, and that's one of the reasons why I like C++ too. Hmm... C++ in kernel space, dunno if that could be easy/possible to do."
Theoritically it's possible to do this, but it may affect the performance adversely. I don't see much difference from C++ try,catch and gotos other than the stack-unwinding.
There was some work done in this regard though.
See http://kerneltrap.org/node/2067 and http://netlab.ru.is/exception/LinuxCXX.shtml
--
Pradeep Padala
http://ppadala.blogspot.com
[Originally posted at 2005-08-17 03:36 pm UTC]
The L4 Pistachio microkernel is implemented in C++:
http://www.l4ka.org
-rian
[Originally posted at 2005-08-13 11:37 am UTC]
Well, it'd be a pain to implement new/delete in a kernel. Other than that, I see no problem.
If you'll look at a typical C kernel, they sort of emulate the concept of classes anyway. Structs + function pointers, etc. I think for something like a kernel that works better than something as complicated as C++. C++ varies too much from compiler to compiler, from name mangling to god knows what else.
I like C a lot but I'm generally not a big fan of C++. It's too hackish, awkward and clunky. If you want an OO language you might as well go for something cleaner.
[Originally posted at 2005-08-17 06:38 pm UTC]
What I was interested about was C++ in the NetBSD kernel. I'm aware of other kernels written in different languages, including C++. FWIW, I think Haiku's kernel has C++ in it (I don't remember correctly now). There is also JNode, written in Java.
IMHO, it'd be really nice to have a system written completely in a programming language that supports objects, and have its system calls/standard library organized that way too.
[Originally posted at 2005-08-14 09:55 pm UTC]
"Invisible gotos" And that's why I don't like C# (and Java).
http://blogs.msdn.com/oldnewthing/archive/2005/01/14/352949.aspx
[Originally posted at 2005-08-17 06:39 pm UTC]
But in your example, you can't do things in between the different else clauses. (OK, my original example lacked this detail, so yours is correct WRT that.)
[Originally posted at 2005-08-09 01:11 pm UTC]
The same thing can ben done without using gotoS
int
foo(...)
{
int error;
/* Check function preconditions here using assert(3). */
if (error condition one) {
error = EFOO;
}
else
if (error condition two) {
error = EBAR;
}
else {
/* Code executed when there are no errors. */
error = 0;
}
/* Release unneeded resources here. */
/* Check function postconditions here using assert(3). */
return error;
}
[Originally posted at 2005-08-17 01:26 am UTC]
Also, another example of error handling with goto's is (and you'll find it in some places over the NetBSD kernel):
function(parameters)
{
...
error = bus_space_alloc(..., handle1);
if (error)
goto out;
error = bus_space_alloc(..., handle2);
if (error)
goto free1;
... [and on, and on...]
free1:
bus_space_free(..., handle1, ...);
out:
return;
}
-- Rui Paulo
[Originally posted at 2005-08-09 02:45 pm UTC]
I've seen usage of goto where I question why switch hasn't been used
instead once a error flag is set. just re
If you fail at allocating resources at some point you'd usually want to free up the resources in the opposite way of the allocation. using a switch statement you can just have it fall trough.
do
{
if ( alloc( res1 ) )
error_cleanup = RES1;
break;
if ( alloc( res2 ) )
error_cleanup = RES2;
break;
if ( alloc( res3 ) )
error_cleanup = RES3;
break;
//all tasks complete?
complete = true;
} while ( complete == false )
switch ( error_cleanup )
{
case RES3:
free(res3);
//fall through.
case RES2:
free(res2);
//fall through.
case RES1:
free(res1);
break;
default:
panic("I didn't expect this one.");
}
I agree with you, but I choose instead to define a macro to do all of my error clean up work, and then call that macro instead.
There are actually two ways of achieving the same effect, including code in between the else clauses. The first is extending the above post:
int
foo(...)
{
int error=0;
/* Check function preconditions here using assert(3). */
if (error condition one) {
error = EFOO;
}
if(!error) if (error condition two) {
error = EBAR;
}
if(!error){
/* Code executed when there are no errors. */
}
/* Release unneeded resources here. */
/* Check function postconditions here using assert(3). */
return error;
}
The second way of achieving the same
is ugly (and basically a hidden goto):
int
foo(...)
{
int i;
int error;
for(i=0;i==0;i++){
/* or use while or switch */
/* Check function preconditions here using assert(3). */
if (error condition one) {
error = EFOO;
break;
}
if (error condition two) {
error = EBAR;
break;
}
/* Code executed when there are no errors. */
error = 0;
}
/* Release unneeded resources here. */
/* Check function postconditions here using assert(3). */
return error;
}
Considering these alternatives I agree that a goto is a more elegant way of implementing this type of function.
Found your page, when searching for goto in c. Interesting article. I find that I needed to use goto, when exiting nested loops.
Since C/C++ doesn't support "break n", which can break out of a specified loop in nested loops, and using a a var, to represent exit state makes code really unweildy, usage of goto seems pretty appropriate.
eg.:
function () {
1: while() {
---
---
---
2: while() {
---
---
---
if (cond1){
goto lbl:
}
}
}
lbl:
#cleanup
return 1;
}
What do you think?!
All the solutions provided to avoid using 'goto' are FAR more spaghetti code than
the initial example. They are not only as ugly as sin but also unmaintainable like a bunch of Perl code.
I searched for goto-advocates because I just implemented a similar function that needs clean up before return: restoring the Lua stack in an objective-c project.
Now I'm sure that it's 'the way to do it right'[tm]
In my professional opinion, there is absolutely nothing wrong with using goto statements as long as there is a state of awareness present. C was designed to allow for low-level programming at lower programmer labor costs than assembler - employing the so called zero-overhead principle. It was also designed to translate fairly straightly into native machine instructions at the time, which were both RISC and CISC machines, something it does very well to this day. And 'goto' is just a quite popular jump instruction in most if not all RISC and CISC architectures. All that is required is a bit of common sense, as there is nothing stopping a C/Assembler programmer to shoot themselves "in the foot" with goto's. Still, let us face it, we use C to hit the golden spot of portability, efficiency and hardware control, and goto may help a lot in certain cases. If you need abstractions, use C++ and mix in C. C++ if used carefully will do just fine in the kernel. Possibly new interface for built-in language facilities has to be bridged between the compiler and the kernel, but the rest should just go unnoticed. After all, C++ emits machine code too, and fairly precisely what the programmer wants, if that programmer is careful with avoiding references, templates, inline calls, cast conversions and virtual methods where these present a danger of machine code ambiguity. My two cents of course.
This use of goto statements hardly seems like good practice. You can do the same thing simply without using gotos.
int
foo(...)
{
int error;
/* Check function preconditions here using assert(3). */
if (!error condition one) {
if (!error condition two) {
/* Code executed when there are no errors. */
error = 0;
} else {
error = EBAR;
}
} else {
error = EFOO;
}
/* Release unneeded resources here. */
/* Check function postconditions here using assert(3). */
return error;
}
Nested loops become a little more difficult, but are still easily manageable without goto statements.
I've been reading up on goto statements, specifically looking for got reasons to use them. I have yet to find a compelling reason to use them.
I just came across your page looking up how to label and goto in C while implementing Knuth's version of a sorted binary tree delete algorithm. I realized after working through this algorithm that it's a perfect example of where goto is not harmful; and, I say that having just read Dijkstra's paper which became titled Goto Considered Harmful.
The algorithm in question could have been implemented by putting the four statements in the return code in each part of the function duplicating it a few times, or I could have nested conditions but when you remember that the goal is readability, not an arbitrary lack of goto statements, this code reads much better.
the discussion on this page is still rolling on (at a glacial rate) :)
Discussions about gotos always seem to focus on how they can confuse the programmer. The other problem is how they confuse the compiler. Some algorithms which might be cleaner with gotos might also benefit massively from compiler optimisation.
Brave optimising compilers may not abandon optimisation where goto is used as a multi-level break or a skip-to-cleanup-at-end-of-function but I don't know.
// You should never output a goto unless you are a compiler.
error_code
with_resources(fn) {
... resources = set_up_resources();
... error_code error = fn(resources);
... resources.release();
... return error;
}
error_code
foo(resources) {
... if (error condition one) {
... ... return EFOO;
... }
... if (error condition two) {
... ... return EBAR;
... }
... execute_code();
... return 0;
}
// later...
with_resources(foo);
// But this is a poor solution -- Mike_A
// here's a better one...
error_code
ConditionTwoHandler(resources) {
... if (can handle condition two) {
... ... handle condition two
... ... return 0;
... }
... else return 1;
}
/* ConditionOneHandler is much the same */
error_code
with_resources(fn, condition_one_handler) {
... resources = Set_up_resources();
... error_code error = fn(resources, condition_one_handler, ConditionTwoHandler);
... release resources;
... return error;
}
error_code
foo(resources, condition_one_handler, condition_two_handler) {
... if (error condition one && condition_one_handler(resources)) {
... ... return EFOO;
... }
... if (error condition two && condition_two_handler()) {
... ... return EBAR;
... }
... execute_code();
... return 0;
}
with_resources(foo, ConditionOneHandler);
/* By making use of the invariant that all arguments that are asked for must be received, explicit responsibility for handling the exception is enforced upon the calling code.
This enforced responsibility can work its way up as far as necessary.
Condition handlers are functions or objects which can be defined externally to client code. This helps decouple the client code from the error handling code, and also improves cohesion. This will always be some coupling when using if/else blocks.
Also, error recovery is improved. If the error can be sorted out where it occurs (signified by the handler returning 0), then the intended code can still be executed without re-running the whole lot (and potentially causing other unexpected side effects)
It's also neat and simple, and this technique can be implemented in pretty much any language (doesn't require language support e.g. as with try/catch keywords and stack unwinding). Another benefit is it is easy to apply to asynchronous code (once you include a handler in the call, you can fire and forget).
-- Mike A
*/
In a switch statement, gotos can be quite helpful:
switcher:
c = getch();
switch(c):
case 'c':
stuff();
break;
case 'd':
stuff();
break;
case 'a':
stuff();
break;
default:
goto switcher;
break; // won't get to this point, but it looks "right".
Post a Comment