C - Non-Local Jumps Using setjmp() and longjmp() in C
Introduction
In C programming, the normal flow of execution moves from one statement to the next and from one function to another through function calls and returns. However, there are situations where a program needs to transfer control directly from one part of the program to another without following the usual function-return sequence. C provides two functions, setjmp() and longjmp(), for performing this type of control transfer.
This mechanism is known as a non-local jump because the jump can move execution from a deeply nested function back to an earlier point in the calling sequence. These functions are declared in the setjmp.h header file.
The setjmp() Function
The setjmp() function saves the current execution environment so that the program can return to that point later.
Its basic syntax is:
#include <setjmp.h>
int setjmp(jmp_buf env);
Here, jmp_buf is a data type used to store the information required to restore the execution environment. The variable env is passed to both setjmp() and longjmp().
When setjmp() is executed directly, it returns 0.
For example:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
int main() {
int value = setjmp(env);
printf("setjmp returned: %d\n", value);
return 0;
}
The output will be:
setjmp returned: 0
The important point is that setjmp() does not simply save a location in the source code. It saves an execution environment that can later be restored by longjmp().
The longjmp() Function
The longjmp() function restores an execution environment previously saved by setjmp().
Its syntax is:
longjmp(env, value);
Here, env must be the jmp_buf previously used with setjmp(), and value is an integer that is returned by setjmp() when execution resumes.
For example:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void test() {
printf("Inside test function\n");
longjmp(env, 10);
}
int main() {
int value = setjmp(env);
if (value == 0) {
printf("Calling test function\n");
test();
} else {
printf("Returned using longjmp: %d\n", value);
}
return 0;
}
Output:
Calling test function
Inside test function
Returned using longjmp: 10
The execution flow is important here. Initially, setjmp(env) returns 0, so the program calls test(). Inside test(), longjmp(env, 10) is executed. Instead of returning normally from test(), program control jumps back to the location where setjmp(env) was originally called.
At that point, setjmp() returns 10 instead of 0.
How setjmp() and longjmp() Work Together
The general execution process can be understood in three stages.
First, setjmp() saves the current execution environment.
Second, the program continues normal execution and may call several functions.
Third, when longjmp() is executed, the previously saved environment is restored and execution continues as though setjmp() has just returned again.
Consider this example:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void functionC() {
printf("Inside function C\n");
longjmp(env, 1);
}
void functionB() {
printf("Inside function B\n");
functionC();
}
void functionA() {
printf("Inside function A\n");
functionB();
}
int main() {
if (setjmp(env) == 0) {
functionA();
} else {
printf("Control returned to main\n");
}
return 0;
}
The output is:
Inside function A
Inside function B
Inside function C
Control returned to main
Here, main() calls functionA(), which calls functionB(), which calls functionC().
When functionC() executes longjmp(), control does not return normally through functionB() and functionA(). Instead, execution jumps directly back to the saved setjmp() point in main().
This is why the mechanism is called a non-local jump.
Understanding the Return Value
One of the most important features of setjmp() is that it can appear to return twice.
The first return happens when setjmp() is executed normally:
setjmp(env);
It returns:
0
The second return happens when a corresponding longjmp() restores the saved environment:
longjmp(env, 5);
The setjmp() expression then returns:
5
Therefore, a common programming pattern is:
if (setjmp(env) == 0) {
/* Normal execution */
} else {
/* Execution after longjmp */
}
This allows the program to distinguish between the initial execution and the execution that occurs after the jump.
The jmp_buf Data Type
The jmp_buf type is defined in setjmp.h.
A program normally declares a variable such as:
jmp_buf env;
The program should not attempt to access or modify the internal contents of jmp_buf. Its implementation is handled by the C library.
The same environment is then used as follows:
setjmp(env);
and:
longjmp(env, value);
Why Non-Local Jumps Are Useful
Non-local jumps can be useful when an error occurs deep inside a sequence of function calls and the program needs to return to a previously established recovery point.
For example, suppose a program has:
main()
|
+-- functionA()
|
+-- functionB()
|
+-- functionC()
If functionC() encounters a serious error, normally the error would need to be passed back through functionB() and functionA().
With setjmp() and longjmp(), a recovery point can be established in main() and control can be transferred back to that point.
A simplified example is:
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void perform_operation() {
printf("Performing operation\n");
printf("An error occurred\n");
longjmp(env, 1);
}
int main() {
if (setjmp(env) == 0) {
perform_operation();
printf("Operation completed\n");
} else {
printf("Error recovery performed\n");
}
return 0;
}
Output:
Performing operation
An error occurred
Error recovery performed
Notice that:
printf("Operation completed\n");
is never executed because longjmp() transfers control away from that point.
Important Difference Between return and longjmp()
A normal return moves control back to the function that called the current function.
For example:
void functionB() {
return;
}
Control goes back to the function that called functionB().
With longjmp(), control can move much farther back:
functionA()
|
+-- functionB()
|
+-- functionC()
|
+-- longjmp()
|
+-- saved point in functionA()
Thus, longjmp() does not follow the ordinary function-return chain.
Important Restrictions
setjmp() and longjmp() must be used carefully because they bypass normal program control flow.
One important issue involves local variables. Consider a function that calls setjmp() and later modifies local variables before longjmp() is executed. The values of certain automatic local variables may not have the values you expect after the jump unless they are appropriately handled. Variables that need to retain reliable values across the jump are generally better declared with suitable storage duration, such as volatile where required by the language rules.
Another important issue is that longjmp() should not be used after the function containing the corresponding setjmp() has already returned. Once the saved execution context is no longer valid, attempting to restore it results in undefined behavior.
For example, this pattern is unsafe:
jmp_buf env;
void save_environment() {
setjmp(env);
}
void other_function() {
longjmp(env, 1);
}
After save_environment() returns, its saved execution context cannot safely be used by longjmp().
Relationship With Resource Management
Another reason to use these functions carefully is resource cleanup.
Suppose a function allocates memory or opens a file:
FILE *file = fopen("data.txt", "r");
If a longjmp() occurs before the file is properly closed, the normal cleanup code may never execute.
For example:
void process() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
longjmp(env, 1);
}
/* Processing */
fclose(file);
}
If the jump occurs after the file has been opened but before fclose(), the cleanup operation could be skipped.
Therefore, programs using non-local jumps need a carefully designed error-recovery strategy.
Difference Between setjmp()/longjmp() and Exceptions
Languages such as C++ provide structured exception handling through try, catch, and throw. C does not have this built-in exception mechanism.
setjmp() and longjmp() can provide a limited form of non-local error handling, but they are not equivalent to modern exception systems.
For example, C++ exception handling can automatically unwind stack objects and invoke destructors. longjmp() does not provide this kind of automatic cleanup.
Therefore, setjmp() and longjmp() should generally be reserved for situations where their behavior is specifically appropriate rather than being used as a replacement for ordinary error handling.
Practical Applications
Some situations where non-local jumps may be encountered include:
-
Implementing certain error-recovery mechanisms.
-
Returning from deeply nested function calls after a serious error.
-
Handling exceptional control-flow situations in specialized systems.
-
Implementing parts of interpreters or runtime systems.
-
Working with low-level C libraries that establish their own recovery mechanisms.
-
Handling certain parser or processing failures.
Advantages
The main advantage is that setjmp() and longjmp() provide a mechanism for transferring control across multiple levels of function calls.
They can simplify certain deeply nested error-recovery paths because the program does not have to manually propagate an error through every intermediate function.
They are also part of the standard C library and therefore available on implementations that support the relevant C standard facilities.
Disadvantages
The major disadvantage is that they make program flow harder to understand.
A reader looking at a function may not immediately know that execution can suddenly jump out of it.
They can also bypass normal cleanup code, potentially causing memory leaks, unclosed files, or other resource-management problems.
Improper use can result in undefined behavior, particularly when the saved execution environment is no longer valid.
For these reasons, ordinary return values, error codes, and structured cleanup are often preferable for routine error handling.
Conclusion
setjmp() and longjmp() provide C with a mechanism for non-local control transfer. setjmp() establishes a recovery point by saving an execution environment, while longjmp() restores that environment and transfers execution back to it.
The key concept is that setjmp() initially returns 0, while a later longjmp() causes the same setjmp() call to return the value supplied to longjmp(). This makes it possible to distinguish normal execution from execution resumed after a jump.
Because non-local jumps can bypass normal function returns and cleanup operations, they should be used carefully and only when their control-flow behavior is appropriate. Understanding these functions is particularly useful for programmers working with low-level C systems, error recovery, and advanced control-flow mechanisms.