What You Will Learn
setjmp() and longjmp() allow a program to jump from a deeply nested function directly back to a previously marked point — even skipping many intermediate function calls. This is useful for centralised error handling but comes with serious pitfalls that you must understand.
setjmp(jmp_buf env) — saves the current execution state (stack pointer, program counter, registers) into env. On the initial call it returns 0.
longjmp(jmp_buf env, int val) — restores the state saved in env, making execution continue from where setjmp() was called, but this time setjmp() “returns” val (not 0). This effectively unwinds all the stack frames between the longjmp() call and the original setjmp() call.
|
Call Stack (before longjmp)
|
longjmp()
unwinds f3, f2, f1 frames ⤴ jumps back to setjmp |
After longjmp(env, 2)
Execution resumes in main()
setjmp(env) now “returns” 2 f1, f2, f3 frames are switch(setjmp(env))
case 0: → initial call case 1: → jumped from f1 case 2: → jumped from f3 ✓ |
| Rule / Pitfall | Explanation |
|---|---|
| Never longjmp after setjmp’s function returned | If the function that called setjmp() has already returned, longjmp() tries to restore a stack frame that no longer exists — undefined behaviour, likely crash |
| Local vars may be clobbered by optimizer | Non-volatile locals in the setjmp function may be reset to pre-setjmp values after longjmp. Declare them volatile to prevent this |
| setjmp must appear in limited contexts | SUSv3: only in entire controlling expression of if/switch/while, or as a standalone call. x = setjmp(env); is NOT standards-conformant |
| longjmp(env, 0) becomes longjmp(env, 1) | Passing 0 as val to longjmp would look like the initial setjmp return. The implementation automatically changes it to 1 |
| env must be global or passed by pointer | Since setjmp and longjmp are in different functions, jmp_buf env is typically declared at global scope so both functions can access it |
Code Examples
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
static jmp_buf jump_point; /* must be global or static */
void deep_function(void)
{
printf("Inside deep_function — about to jump back\n");
longjmp(jump_point, 1); /* Jump back to setjmp site */
printf("This line is NEVER reached\n");
}
void middle_function(void)
{
printf("Inside middle_function — calling deep\n");
deep_function();
printf("This line is also NEVER reached\n");
}
int main(void)
{
int result = setjmp(jump_point); /* Mark the jump target */
if (result == 0) {
/* First time — normal execution */
printf("setjmp returned 0 — initial setup\n");
middle_function();
} else {
/* We arrived here via longjmp */
printf("Back in main! longjmp returned %d\n", result);
printf("middle_function and deep_function frames are gone\n");
}
return 0;
}
/* Output:
setjmp returned 0 — initial setup
Inside middle_function — calling deep
Inside deep_function — about to jump back
Back in main! longjmp returned 1
middle_function and deep_function frames are gone */
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
static jmp_buf err_jmp;
/* Simulate two different error conditions */
void parse_data(int data)
{
if (data < 0)
longjmp(err_jmp, 1); /* error type 1: negative */
if (data > 100)
longjmp(err_jmp, 2); /* error type 2: too large */
printf(" parse_data(%d) OK\n", data);
}
void process(int data)
{
printf("process(%d) called\n", data);
parse_data(data); /* may longjmp from here */
printf("process(%d) finished normally\n", data);
}
int main(void)
{
int inputs[] = { 42, -5, 200, 10 };
for (int i = 0; i < 4; i++) {
int err = setjmp(err_jmp);
if (err == 0) {
process(inputs[i]);
} else if (err == 1) {
printf(" ERROR: negative value rejected!\n");
} else if (err == 2) {
printf(" ERROR: value too large!\n");
}
printf("---\n");
}
return 0;
}
/* Output:
process(42) called
parse_data(42) OK
process(42) finished normally
---
process(-5) called
ERROR: negative value rejected!
---
process(200) called
ERROR: value too large!
---
process(10) called
parse_data(10) OK
process(10) finished normally
--- */
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
static jmp_buf env;
void do_jump(void)
{
longjmp(env, 1);
}
int main(void)
{
int normal_var = 10; /* may be clobbered by optimizer */
volatile int safe_var = 10; /* volatile: optimizer won't cache in register */
if (setjmp(env) == 0) {
normal_var = 99; /* changed before longjmp */
safe_var = 99; /* changed before longjmp */
do_jump();
} else {
/* After longjmp with -O2 optimization:
normal_var may be reset to 10 (pre-setjmp value)
safe_var is guaranteed to be 99 (volatile preserved) */
printf("normal_var = %d (may be 10 or 99 — undefined without volatile)\n",
normal_var);
printf("safe_var = %d (always 99 — volatile guarantees it)\n",
safe_var);
}
return 0;
}
/* Lesson: always declare locals in the setjmp() function
as volatile if their values matter after a longjmp.
Compile with: gcc -O2 -o test test.c to see the difference. */
Interview Preparation Questions
A normal goto can only jump within the same function. A nonlocal goto (via longjmp) can jump across function boundaries — from a deeply nested function back to a parent or grandparent function. It physically resets the stack pointer and program counter to the state saved by setjmp().
On the initial (first) call, setjmp() returns 0. After a longjmp(env, N) brings execution back to that point, setjmp() “returns” N. The program uses this return value (typically in a switch) to know whether it arrived normally or via longjmp, and from which source.
Primarily: the stack pointer register (so the stack can be unwound back to the right frame) and the program counter register (so execution resumes at the right instruction). It also saves other CPU registers needed to restore the execution context.
Undefined behaviour — this is “stack unwinding into a non-existent frame.” The saved stack pointer now points to memory that has since been reused for other function calls. The result is typically a crash, infinite loop, or silent data corruption. This is one of the most dangerous misuses of longjmp.
An optimizing compiler may keep local variables in CPU registers instead of RAM. When longjmp() restores register values from the jmp_buf snapshot (taken at setjmp time), any changes made to those variables between setjmp and longjmp are lost. volatile forces the compiler to always read/write the variable from actual memory, preventing this.
longjmp() does not restore the signal mask (the set of blocked signals). siglongjmp() does — and only if sigsetjmp() was called with savesigs=1. When using setjmp/longjmp inside a signal handler, always use the sig* variants to ensure the signal mask is correctly restored.
