How setjmp and longjmp Work in C
In the C programming language, setjmp and
longjmp provide a low-level mechanism for executing
nonlocal jumps, allowing control flow to transfer directly from one
function back to a previously executed ancestor frame on the call stack.
This article examines the low-level execution context used by these
functions, how they manipulate the CPU registers and stack pointer, and
the implications this bypass of normal function return semantics has on
memory and variable state.
The Role of Nonlocal Jumps
Standard C control-flow constructs, such as goto, only
permit branching within the local scope of a single function. In
contrast, nonlocal jumps traverse the call hierarchy, enabling a deeply
nested function to transfer execution directly back to an earlier
caller. This pattern is primarily used for low-level error handling,
exception emulation, and implementing cooperative multitasking or
coroutines.
The Low-Level Mechanism: Saving and Restoring Execution Context
The mechanics of setjmp and longjmp rely on
manipulating the process's execution context directly at the
architecture level via a special buffer type, jmp_buf.
The jmp_buf array stores the minimum hardware state
necessary to resume execution at a specific point in the program. While
the exact contents depend on the target CPU architecture and calling
convention, a jmp_buf typically preserves:
- Program Counter (Instruction Pointer): The address of the next machine instruction to execute.
- Stack Pointer: The memory address pointing to the current top of the call stack.
- Frame Pointer / Base Pointer: The register defining the local stack frame boundary.
- Callee-Saved Registers: General-purpose registers that calling conventions dictate must be preserved across function boundaries.
How setjmp Captures
the State
When setjmp(env) is called, the function executes
architecture-specific assembly instructions to copy the current values
of the CPU's register set into the provided env buffer.
When executed directly by the program, setjmp stores
this state and returns 0. The CPU continues executing the
subsequent lines of code normally, but the snapshot stored within
jmp_buf remains intact in memory for future reference.
How longjmp
Restores the State
The longjmp(env, val) function triggers the jump by
reading the saved machine registers out of env and loading
them directly back into the CPU.
- Stack Pointer Reset: The stack pointer is reset to
the value captured during
setjmp. Any stack frames pushed onto the call stack since the invocation ofsetjmpare discarded instantly without traditional epilogues or cleanup. - Register Restoration: Callee-saved registers are overwritten with their saved values.
- Execution Redirection: The instruction pointer is
updated to point immediately after the original
setjmpcall site. - Return Value Substitution: The CPU resumes
execution as if
setjmphas just returned for a second time, evaluating to the integer provided byval(or1ifvalwas set to0).
Critical Considerations
Because longjmp bypasses standard function return
sequences, it introduces specific runtime behaviors:
- No Automatic Cleanup: Local resources allocated
between
setjmpandlongjmp—such as dynamically allocated heap memory, file descriptors, or mutex locks—are not released automatically, often leading to resource leaks. - Dangling Frames: Invoking
longjmpon anenvbuffer initialized inside a function that has already returned results in undefined behavior, as the target stack frame no longer exists. - Compiler Optimizations and
volatile: Compilers often keep local variables in registers rather than on the stack. If a non-volatile local variable is modified aftersetjmpand beforelongjmp, its value becomes indeterminate upon the jump back. Declaring such variables with thevolatilequalifier forces the compiler to read and write them directly from memory, ensuring their values remain consistent after a nonlocal jump.