setjmp.h is a Header file defined in the C standard library to provide "non-local jumps": control flow that deviates from the usual subroutine call and return sequence. The complementary functions setjmp and longjmp provide this functionality.
A typical use of setjmp/longjmp is implementation of an exception mechanism that exploits the ability of longjmp to reestablish program or thread state, even across multiple levels of function calls. A less common use of setjmp is to create syntax similar to coroutines.
setjmp saves the current environment (the program state), at some point of program execution, into a platform-specific data structure (jmp_buf) that can be used at some later point of program execution by longjmp to restore the program state to that saved by setjmp into jmp_buf. This process can be imagined to be a "jump" back to the point of program execution where setjmp saved the environment. The (apparent) return value from setjmp indicates whether control reached that point normally (zero) or from a call to longjmp (nonzero). This leads to a common idiom:
POSIX.1 does not specify whether setjmp and longjmp save and restore the current set of blocked signals; if a program employs signal handling it should use POSIX's sigsetjmp/siglongjmp.
The C99 Rationale describes jmp_buf as being an array type for backward compatibility; existing code refers to jmp_buf storage locations by name (without the & address-of operator), which is only possible for array types. C99 Rationale, version 5.10, April 2003, section 7.13 It notes that it can simply be a one-member-long array with its single member being the actual data; indeed, this is the approach employed by the GNU C library, which defines the type as struct __jmp_buf_tag[1].
If the function in which setjmp was called returns, it is no longer possible to safely use longjmp with the corresponding jmp_buf object. This is because the stack frame is invalidated when the function returns. Calling longjmp restores the stack pointer, which—because the function returned—would point to a non-existent and potentially overwritten or corrupted stack frame. CS360 Lecture Notes — Setjmp and Longjmp setjmp(3)
Similarly, C99 does not require that longjmp preserve the current stack frame. This means that jumping into a function which was exited via a call to longjmp is undefined. ISO/IEC 9899:1999, 2005, 7.13.2.1:2 and footnote 211
static jmp_buf buf;
void second() {
void first() {
int main() {
printf("second\n"); // prints
longjmp(buf, 1); // jumps back to where setjmp was called - making setjmp now return 1
}
second();
printf("first\n"); // does not print
}
if (!setjmp(buf))
first(); // when executed, setjmp returned 0
else // when longjmp jumps back, setjmp returns 1
printf("main\n"); // prints
return 0;
}
When executed, the above program will output:
Notice that although the first() subroutine gets called, "first" is never printed, as second() never returns control to first(). Instead, "main" is printed when the conditional statement if (!setjmp(buf)) is checked a second time.
Following these rules can make it easier for the implementation to create the environment buffer, which can be a sensitive operation. More general use of setjmp can cause undefined behaviour, such as corruption of local variables; conforming compilers and environments are not required to protect or even warn against such usage. However, slightly more sophisticated idioms such as are common in literature and practice, and remain relatively portable. A simple conforming methodology is presented below, where an additional variable is maintained along with the state buffer. This variable could be elaborated into a structure incorporating the buffer itself.
In a more modern-looking example, the usual "try" block would be implemented as a setjmp (with some preparation code for multilevel jumps, as seen in ), the "throw" as longjmp with the optional parameter as the exception, and the "catch" as the "else" block under "try".
static void first();
static void second();
/* Use a file scoped static variable for the exception stack so we can access
int main(void) {
static void first() {
static void second() {
* it anywhere within this translation unit. */
static jmp_buf exception_env;
static int exception_type;
char* volatile mem_buffer = NULL;
if (setjmp(exception_env)) {
// if we get here there was an exception
printf("first failed, exception type: %d\n", exception_type);
} else {
// Run code that may signal failure via longjmp.
puts("calling first");
first();
mem_buffer = malloc(300); // allocate a resource
printf("%s\n", strcpy(mem_buffer, "first succeeded")); // not reached
}
free(mem_buffer); // NULL can be passed to free, no operation is performed
return 0;
}
jmp_buf my_env;
puts("entering first"); // reached
memcpy(my_env, exception_env, sizeof my_env); // store value of exception_env in my_env since exception_env will be reused
switch (setjmp(exception_env)) {
case 3: // if we get here there was an exception.
puts("second failed, exception type: 3; remapping to type 1");
exception_type = 1;
default: // fall through
memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
longjmp(exception_env, exception_type); // continue handling the exception
case 0: // normal, desired operation
puts("calling second"); // reached
second();
puts("second succeeded"); // not reached
}
memcpy(exception_env, my_env, sizeof exception_env); // restore exception stack
puts("leaving first"); // never reached
}
puts("entering second" ); // reached
exception_type = 3;
longjmp(exception_env, exception_type); // declare that the program has failed
puts("leaving second"); // not reached
}
This program's output is:
This is exploited by thread libraries to provide cooperative multitasking facilities without using [[setcontext]] or other fiber facilities.
Considering that setjmp to a child function will generally work unless sabotaged, and setcontext, as part of POSIX, is not required to be provided by C implementations, this mechanism may be portable where the setcontext alternative fails.
Since no exception will be generated upon overflow of one of the multiple stacks in such a mechanism, it is essential to overestimate the space required for each context, including the one containing main() and including space for any signal handlers that might interrupt regular execution. Exceeding the allocated space will corrupt the other contexts, usually with the outermost functions first. Unfortunately, systems requiring this kind of programming strategy are often also small ones with limited resources.
jmp_buf mainTask, childTask;
void call_with_cushion();
void child();
int main() {
void call_with_cushion() {
void child() {
if (!setjmp(mainTask)) {
call_with_cushion(); // child never returns, yield
} // execution resumes after this "}" after first time that child yields
while (1) {
printf("Parent\n");
if (!setjmp(mainTask))
longjmp(childTask, 1); // yield - note that this is undefined under C99
}
}
char space[1000]; // Reserve enough space for main to run
space[999] = 1; // Do not optimize array out of existence
child();
}
while (1) {
printf("Child loop begin\n");
if (!setjmp(childTask))
longjmp(mainTask, 1); // yield - invalidates childTask in C99
printf("Child loop end\n");
if (!setjmp(childTask))
longjmp(mainTask, 1); // yield - invalidates childTask in C99
}
/* Don't return. Instead we should set a flag to indicate that main()
should stop yielding to us and then longjmp(mainTask, 1) */
}
|
|