6.8 Nonlocal Goto: setjmp() and longjmp()

6.8 Nonlocal Goto: setjmp() and longjmp()
Linux System Programming — Chapter 6
2
Key Functions
3
Code Examples
6
Interview Questions

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.

How setjmp() and longjmp() Work

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 Flow: setjmp → deep call → longjmp jumps back

Call Stack (before longjmp)
main()
setjmp(env) called here ← TARGET
f1() called by main
f2() called by f1
f3() — longjmp(env, 2) called here
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
gone — stack unwound

switch(setjmp(env))
case 0: → initial call
case 1: → jumped from f1
case 2: → jumped from f3 ✓

Rules and Pitfalls
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
Best Practice: Avoid setjmp/longjmp whenever possible. They make code extremely hard to read and maintain. Prefer error return codes, errno, or (in C++) exceptions. Use them only for signal handler error recovery if absolutely necessary — prefer sigsetjmp/siglongjmp in that case.

Code Examples

Example 1: Basic setjmp / longjmp Jump
#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 */
Example 2: Error Handling with longjmp (Multiple Jump Sources)
#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
   --- */
Example 3: The volatile Keyword with longjmp
#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

Q1. What is a nonlocal goto? How is it different from a normal goto?

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().

Q2. What does setjmp() return on initial call vs after longjmp()?

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.

Q3. What information does setjmp() save in the jmp_buf?

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.

Q4. What happens if longjmp() is called after the function with setjmp() has returned?

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.

Q5. Why must local variables be declared volatile when using setjmp/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.

Q6. What is the difference between longjmp() and sigsetjmp()/siglongjmp()?

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.

Leave a Reply

Your email address will not be published. Required fields are marked *