We often write some functions which have more than one exit point (that is, return
in C). At the same time, when exiting the function, for some general works such as resource cleanup, we wish to implement them only once, rather than implementing them at every exit point. Typically, we may achieve our wish by using goto like the following:
void f() { ... ...{..{... if(exit_cond) goto f_exit; }..}.. ... f_exit: some general work such as cleanup }
I think using goto here is acceptable, and I know many people agree on using goto here. Just out of curiosity, does there exist any elegant way for neatly exiting a function without using goto in C?
Alternatives to the “goto” are “break” and “continue”.
Use of goto can be simply avoided using break and continue statements.
From the early days of programming (i.e. assembler programming) the replacement for goto directives is so-called structured programming. This means using if-then-else statements, for- and while-loops, switch statements, break and continue, etc.
The exit() function in C. The exit() function is used to terminate a process or function calling immediately in the program. It means any open file or function belonging to the process is closed immediately as the exit() function occurred in the program.
goto
?The problem you want to solve is: How to make sure some common code always gets executed before the function returns to the caller? This is an issue for C programmers, since C does not provide any built in support for RAII.
As you already concede in your question body, goto
is a perfectly acceptable solution. Never-the-less, there may be non-technical reasons to avoid using it:
There are always more than one way to skin a cat, but elegance as a criteria is too subjective to provide a way to narrow to a single best alternative. You have to decide the best option for yourself.
If avoiding an explicit jump (e.g., goto
or break
) common cleanup code can be encapsulated within a function, and explicitly called at the point of early return
.
int foo () { ... if (SOME_ERROR) { return foo_cleanup(SOME_ERROR_CODE, ...); } ... }
(This is similar to another posted answer, that I only saw after I initially posted, but the form shown here can take advantage of sibling call optimizations.)
Some people feel explicitness is more clear, and therefore more elegant. Others feel the need to pass cleanup arguments to the function to be a major detractor.
Without changing the semantics of the user API, change its implementation into a wrapper composed of two parts. Part one performs the actual work of the function. Part two performs the cleanup necessary after part one is done. If each part is encapsulated within its own function, the wrapper function has a very clean implementation.
struct bar_stuff {...}; static int bar_work (struct bar_stuff *stuff) { ... if (SOME_ERROR) return SOME_ERROR_CODE; ... } int bar () { struct bar_stuff stuff = {}; int r = bar_work(&stuff); return bar_cleanup(r, &stuff); }
The "implicit" nature of the cleanup from the point of view of the function that performs the work may be viewed favorably by some. Some potential code bloat is also avoided by only calling the cleanup function from a single place. Some argue that "implicit" behaviors are "tricky", and therefore more difficult to understand and maintain.
More esoteric solutions using setjmp()
/longjmp()
can be considered, but using them correctly can be difficult. There are open-source wrappers that implement try/catch exception handling style macros over them (for example, cexcept
), but you have to change your coding style to use that style for error handling.
One could also consider implementing the function like a state machine. The function tracks progress through each state, an error causes the function to short circuit to the cleanup state. This style is usually reserved for particularly complex functions, or functions that need to be retried later and be able to pick up from where they left off.
If you need to comply to coding standards, then the best approach is to follow whatever technique is most prevalent in the existing code base. This applies to almost all aspects of making changes to an existing stable source code base. It would be considered disruptive to introduce a new coding style. You should seek approval from the powers that be if you feel a change would dramatically improve some aspect of the software. Otherwise, as "elegance" is subjective, arguing for the sake of "elegance" is not going to get you anywhere.
For example
void f() { do { ... ...{..{... if(exit_cond) break; }..}.. ... } while ( 0 ); some general work such as cleanup }
Or you could use the following structure
while ( 1 ) { //... }
The main advantage of the structural approach contrary to using goto statements is that it introduces a discipline in writing code.
I am sure and have enough experience that if a function has one goto statement then through some time it will have several goto statements.:)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With