C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
124: Non-Local Jumps with setjmp and longjmp
Wait, why would I ever use this instead of just returning an error code?
I get this question a lot. In a small program, returning an integer error code up the call stack is fine. But imagine you're writing a recursive descent parser or a deeply nested tree traversal—maybe you're ten or fifteen functions deep into a complex data structure—and you hit a fatal error. If you use return codes, every single one of those fifteen functions has to check for that error and pass it upward. It creates a mountain of boilerplate code: if (result == ERR_FATAL) return ERR_FATAL; repeated everywhere.
setjmp and longjmp let you "teleport" back to a known safe state. You set a marker (a jmp_buf) at the top level, and the moment you hit a disaster deep in the recursion, you just jump straight back to that marker, skipping all the intermediate stack frames entirely. It's essentially C's primitive version of try-catch blocks.
How do I actually trigger the jump without confusing the program?
The trickiest part is that setjmp returns twice. The first time is when you're actually calling it to save the state; it returns 0. The second time is when longjmp sends you back there; it returns the value you passed into longjmp.
Here is a realistic example. Let's say we're evaluating a mathematical expression recursively, and we want to jump out the moment we encounter a division by zero, regardless of how deep we are.
#include <stdio.h>
#include <setjmp.h>
jmp_buf recovery_point;
void deep_eval(int level) {
printf("Evaluating level %d...\n", level);
if (level > 5) {
printf("Oops! Division by zero at level %d!\n", level);
longjmp(recovery_point, 1); // Jump back with value 1
}
deep_eval(level + 1);
}
int main() {
// Save the current environment
int val = setjmp(recovery_point);
if (val == 0) {
printf("Starting evaluation...\n");
deep_eval(1);
} else {
// We arrive here ONLY after a longjmp
printf("Recovered from error. Error code: %d\n", val);
}
return 0;
}
I always tell people to treat the if (val == 0) block as the "try" block and the else block as the "catch" block.
What's the catch? Where does this usually break?
There are two major traps. First: local variables. If you modify a local variable between the setjmp call and the longjmp call, its value is technically indeterminate after the jump unless you declare it as volatile. The compiler might have cached that variable in a register, and longjmp restores the registers to exactly how they were during setjmp, effectively "undoing" your changes.
Second, and much more dangerous: never jump into a function that has already returned. If you call setjmp inside a function, and that function finishes and returns to its caller, the stack frame is gone. If you then call longjmp to jump back to that jmp_buf, you are jumping into a dead area of the stack. Your program will likely segfault, or worse, exhibit weird, non-deterministic behavior that will take you three days to debug.
My rule of thumb? Only keep your jmp_buf in a scope that is guaranteed to stay alive for the entire duration of the risky operations. Global variables or a dedicated "context" struct are the safest bets.
📋 Practical Task
Implementing a Recursive Expression Parser Error Recovery System
You are building a simple recursive parser that processes a simulated stream of tokens. Some tokens are "valid," and some are "corrupt." Because the parser is deeply recursive, you need to implement a non-local jump to handle corruption errors without polluting every function with error-checking logic.
Requirements:
- Create a
jmp_bufto serve as the recovery point. - Write a recursive function
parse_node(int depth)that calls itself up to 10 times. - Inside
parse_node, simulate a random "corruption error" using a condition (e.g., ifdepth == 7). - When a corruption error occurs, use
longjmpto return to the main loop with a specific error code (e.g., 404). - In your
mainfunction, usesetjmpto catch this jump and print a message indicating that the parser recovered from a corruption error at the specific error code provided.
There are no comments for now.