So I would like to be able to call a function like this:
void func(1, 2, 3, (void*)label) // can return normal or to the labels
//some code
label:
//different code
Is it possible, and is it bad practice?
So I would like to be able to call a function like this:
void func(1, 2, 3, (void*)label) // can return normal or to the labels
//some code
label:
//different code
Is it possible, and is it bad practice?
In standard C99 or C11 you cannot.
But GCC has a language extension, label as values, which might help (and Clang/LLVM also accepts it).
Even with the computed gotos that extension provides, jumping into another routine is (nearly) undefined behavior. You can jump indirectly to a label inside the same routine. You could return a label to jump into; that is, goto *fun(&&label1, &&label2, x, y);
with fun(void*l1, void*l2, int x, int y)
returning either one (l1
or l2
) of its two first arguments. You can have threaded code.
Notice that on many processors, an indirect jump (or call) is slower than a direct one (because it hurts the branch predictor). So I don't think that returning a label for an indirect goto
is good practice (so John Bode's answer suggesting a switch
is better), but you might have weird corner cases.
Be also aware that recent GCC (i.e. GCC 6 in june 2016) can sometimes optimize to make a tail-call.
Read also about continuation-passing style (and also about continuations & delimited continuations, call/cc in Scheme, callbacks, closures, and perhaps event loops; Prolog & its backtracking abilities; coroutines; iterators; threads; exceptions; operational semantics & denotational semantics). It could inspire you; look inside the implementation of Chicken Scheme and read the Cheney on the MTA paper and the SICP book. If you are generating some C code (e.g. from some Lisp-like language) or want to understand how Lisp or Scheme works, read also Queinnec's Lisp In Small Pieces book.
For low-level exception handling in C, longjmp (with setjmp
) is the preferred way (see also the obsolete, Unix specific, setcontext(3)...; look into backtrace(3)...). But be careful. See this & that.
You can (see the Labels as Values gcc extension). It's rather doubtful that you should.
Dijkstra taught us that goto was harmful. You are proposing not only using goto, and a jump table, but also a function that returns to the passed label.
Understand that a function is essentially a goto (jump) with some pushing onto the stack (the stack frame). That frame is pop'ed on a return to put the stack into an expected state. If you mean to subvert the function's return to its call location, to instead go to the passed label, then wherever you go had better know how to clean up the stack and either have it's own way to return to the main block of code or some idea how the program should continue from here.
Tricks like this are how functions work in the first place. If you want, you can design your own function protocol this way. The question is why? Functions do this already. You can solve many problems that might need this with functions, and if you need to get fancy, function pointers.
It is not possible, and if it were, it would probably be considered bad practice1. Gotos within a function make code impossible to trace by inspection; gotos across functions would make the situation much worse.
Here's an example:
i = 4;
label: printf( "%d\n", i );
What value actually gets printed? Is it 4
? Is it ever 4
? I can't know until I chase down every instance of goto label
in that function. Suppose I discover something like this:
i = 2;
goto label;
...
i = 4;
label: printf( "%d\n", i );
In that scenario, i
is never 4
; that assignment is unconditionally skipped over. Now, imagine allowing that sort of thing to happen across functions, and the work just gets uglier.
You can kind-of-sort-of fake exception handling using setjmp
and longjmp
, but that's not exactly what you're looking for. Honestly, I'd have func
return an error code and branch based on that:
switch ( func( 1, 2, 3 ) )
{
case NORMAL:
// normal processing
break;
case THIS_ERROR:
// error processing
break;
case THAT_ERROR:
// error processing
break;
...
}
or
if ( func( 1, 2, 3 ) )
{
// normal processing
}
else
{
// error processing
}