Since I started programming in C until a few months ago I religiously practiced the rule “Don’t use goto” (totaling for about 23 years of abstinence). I remember I was puzzled at first – coming from BASIC programming, I hardly believed you could get along without the infamous instruction. To change my habits I took this as a challenge, and in a short time I was able to get rid of the evil statement.
In practice I was helped by a bunch of C statements that are basically disguised goto instructions: break, continue, and return.
Break and continue allows you to jump out of a loop or at the next iteration; while return is a jump out of the current function.
Single exit point (i.e. just one return per function) is often preached as a “Right Thing”, but when programming in C, single exit fights madly with error management, forcing you either to deeply nest conditionals or to add boolean variables with the sole purpose of skipping code in case of error.
Amiga was the first computer I programmed in C, it was an advanced machine for that time, but experimental in many ways. For example Amiga operating system provided you with full multitasking capabilities, but the hardware lacked an MMU therefore no protected memory was in place. This forced the programmer to be very careful about error conditions – one unhandled error and the entire system could be nuked by a single failing program.
That’s probably why I have been always attentive to error handling and graceful exit.
It was back then that I started using the idiom:
bool ok1; bool ok2; bool ok3; ok1 = f1(); ok2 = f2(); ok3 = f3(); if( ok1 && ok2 && ok3 ) { // f1(), f2() and f3() returned ok. } if( ok1 ) free1(); if( ok2 ) free2(); if( ok3 ) free3();
This helps to avoid some nesting but fails in tracking which function succeeded and which didn’t. That could be fine in some situations, but not in others. For example, if you have to free some resources allocated in f2(), you must know if f2() succeeded.
Conversely, the idiom below:
bool ok1; bool ok2; bool ok3; ok1 = f1(); ok2 = f2(); ok3 = f3(); if( ok1 && ok2 && ok3 ) { // f1(), f2() and f3() returned ok. } if( ok1 ) free1(); if( ok2 ) free2(); if( ok3 ) free3();
Performs proper cleanup, but fails to capture that f2() has to be executed if, and only if, f1() succeeds.
Then I went the C++ way for several years and gained a markedly object-oriented approach.
Using C++ you don’t have to worry much about these details if you happen to use the RAII idiom. That is, an automatic object (i.e. local instances) gets automatically destroyed when the scope is left regardless of the reason that causes the execution to leave the scope.
In other words, if a function fails, be it with an exception or by reporting a specific error and triggering a return, objects that were built are destroyed, leaving the system in a good, non-leaking state.
Fast forward some years I am back to C programming with a heavy legacy of object-oriented approach. This means that I try to design modules in an Object-oriented way – modules define classes, and each class has one constructor that prepares the instance for usage. Each class also has one destructor (that may be empty, but this is an implementation detail, so if it changes in the future you don’t have to change the calling code).
This is the setting were the C error management issue arose again. I want to mimic a C++-like behavior so that when in the constructor there are 3 “sub-objects” to construct I want proper clean up (i.e. destructor calls) are invoked in case of error.
If you follow a strictly structured approach (without exception support), you get a very convoluted code:
if( f1_ctor() ) { if( f2_ctor() ) { if( f3_ctor() ) { // succesfull return true; } else { f2_dtor(); f1_dtor(); } } else { f1_dtor(); } } return false;
The lack of “fall through” semantics forces you to duplicate code and therefore makes the coding and the maintenance more error-prone. In fact, suppose you have to add a third call f0_ctor() that must be called before f1_ctor(). Then you have to change nearly everything, indentation included.
It’s time to reconsider my mind framework. I would need something that selects a portion of the “destructor” sequence. Something like a switch with fall through:
progress = 1; if( f1_ctor() ) { progress = 2; if( f2_ctor() ) { progress = 3; if( f3_ctor() ) { progress = 0 } } } switch( progress ) { case 0: return true; case 3: f2_dtor(); // fall through case 2: f1_dtor(); // fall through case 1: return false; }
This can do, it is somewhat error prone when writing and/or changing the code. If you duplicate one of the progress codes you get a wrong cleanup that can go undetected.
Moreover, it doesn’t seem to add much to the goto-based error management:
if( !f1_ctor() ) { goto error1; } if( !f2_ctor() ) { goto error2; } if( !f3_ctor() ) { goto error3; } return true; error3: f2_dtor(); error2: f1_dtor(); error1: return false;
This notation is more terse (thus more readable) and appears to be more robust than the previous ones.
So why should I refrain from the goto statement in this case? There isn’t any good motivation.
I don’t want to provide any sort of “free for all” authorization in the wild usage of goto instruction. On the contrary, my claim is that first, you have to come of age without using goto (i.e. write programs for at least 18 years), practice Object Oriented Programming, and carefully deal with error handling, then if you find yourself in a language lacking suitable error management, you may use goto… only if everything else is worse.
One thought on “Considering Goto Harmful, but…”