The other thread on goto: Lead me to want to ask... In the spirit of
state machines and coroutines, This n00b to C would like to know if
setjmp/longjmp are the only way to break out of a routine;
As others noted, the answer to this question as stated is "no",
because ordinary "return" breaks out of a routine, returning to
its caller. But the answer to the question I believe you intended
is "yes".
leave the function and re-enter the stack state.
Given this and the subject line ("coroutines"), it sounds as though
you wish to use longjmp() to resume execution in a function that
earlier used setjmp() to terminate its own execution, e.g.:
void f(void) {
... do some work ...
if (setjmp(label1))
goto resume;
... do some more work ...
longjmp(label2, 1); /* "suspend" function f() */
resume:
... do yet more work ...
... ordinary function-return here ...
}
void g(void) {
... do some work ...
if (setjmp(label2))
goto resume;
... do some more work ...
f(); /* f() will longjmp to label2 */
return;
label2:
/* we got here because f() executed a longjmp() */
... do more work ...
longjmp(label1, 1); /* go back into f() */
/* NOTREACHED */
}
This is not "legal" in Standard C, and some implementations --
including more than one that I wrote myself -- will abort your
program at the "longjmp(label1, 1)" in g(), when they discover
that you are attempting to "reactivate" a stack frame that you
abandoned earlier (by executing the longjmp() in f()).
The key insight here is that longjmp() does not mean "suspend"; it
means "quit, and destroy all local variables". To implement
coroutines, you must *not* destroy the local variables. How this
can be done (and indeed, whether it is even possible) is
machine-dependent, and sometimes compiler-dependent. The longjmp
function is not guaranteed to do this, and does not work on real
implementations.
I ask this because I've only briefly, as yet, reviewed these functions
and I believe longjmp will only return a designated int.
This is correct; but the situation is considerably worse: longjmp()
may also destroy variables that are local to the function that used
setjmp(). To prevent this, such variables must be declared using
the "volatile" qualifier. In addition, the return value from setjmp()
cannot be stored in a variable! That is, the Standard renders the
effect of:
int x; /* or even "volatile int x" */
...
x = setjmp(jmpbuf);
undefined:
Environmental restriction
[#4] An invocation of the setjmp macro shall appear only in
one of the following contexts:
- the entire controlling expression of a selection or
iteration statement;
- one operand of a relational or equality operator with
the other operand an integer constant expression, with
the resulting expression being the entire controlling
expression of a selection or iteration statement;
- the operand of a unary ! operator with the resulting
expression being the entire controlling expression of a
selection or iteration statement; or
- the entire expression of an expression statement
(possibly cast to void).
[#5] If the invocation appears in any other context, the
behavior is undefined.
What this means is that to use longjmp(), the programmer must in
general implement his own stack of activations, e.g.:
struct Goto {
struct Goto *next;
volatile int longjmp_value1; /* can use volatile here */
volatile char *longjmp_value2; /* to avoid needing it below */
... etc ...
jmp_buf buf;
};
struct Goto *Goto_tos; /* current top of Goto stack */
...
void f(void) {
struct Goto g; /* need "volatile" if not in the struct */
...
g.next = Goto_tos; /* push ourselves on the stack */
Goto_tos = g;
if (setjmp(g.buf))
goto got_jumped_to;
... normal flow of control ...
return;
got_jumped_to:
Goto_tos = g.next; /* pop ourselves off the stack */
... the value(s) from longjmp are in g.longjmp_valueN ...
... "gone-to" flow of control ...
}
void g(void) {
... normal flow of control ...
if (some exceptional condition) {
/* abort, ripping control away from all callers until top
of goto stack */
Goto_tos->longjmp_value1 = ...;
Goto_tos->longjmp_value2 = ...;
longjmp(Goto_tos->buf, 1);
}
... continue normal flow of control ...
}
Note that as soon as you start doing this, all functions involved
in the path between f() and g() that need to clean up (e.g., close
a file or free memory) start having to catch longjmp()s. In this
case, the "goto stack" structure provides the same thing Lisp
systems provide via "unwind-protect".