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.

  1. 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 of setjmp are discarded instantly without traditional epilogues or cleanup.
  2. Register Restoration: Callee-saved registers are overwritten with their saved values.
  3. Execution Redirection: The instruction pointer is updated to point immediately after the original setjmp call site.
  4. Return Value Substitution: The CPU resumes execution as if setjmp has just returned for a second time, evaluating to the integer provided by val (or 1 if val was set to 0).

Critical Considerations

Because longjmp bypasses standard function return sequences, it introduces specific runtime behaviors:

  • No Automatic Cleanup: Local resources allocated between setjmp and longjmp—such as dynamically allocated heap memory, file descriptors, or mutex locks—are not released automatically, often leading to resource leaks.
  • Dangling Frames: Invoking longjmp on an env buffer 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 after setjmp and before longjmp, its value becomes indeterminate upon the jump back. Declaring such variables with the volatile qualifier forces the compiler to read and write them directly from memory, ensuring their values remain consistent after a nonlocal jump.