Why longjmp Clobbers Non-Volatile Variables in C
In the C programming language, invoking longjmp can
restore or corrupt the values of local automatic variables that were
modified after the corresponding setjmp call. This occurs
because modern optimizing compilers cache local variables in CPU
registers rather than keeping them synchronized in system memory. When
longjmp executes, it restores the CPU registers to the
exact snapshot captured during setjmp, overwriting any
register-cached changes unless the variable is explicitly declared with
the volatile type qualifier.
The Mechanics of
setjmp and longjmp
The setjmp and longjmp functions provide a
mechanism for non-local jumps, bypassing normal function call and return
conventions.
setjmp(jmp_buf env): Saves the execution environment—primarily CPU control registers, including the stack pointer (SP), frame pointer (FP), instruction pointer (IP), and any callee-saved registers—into theenvbuffer.longjmp(jmp_buf env, int val): Restores the CPU registers fromenvand transfers control back to the point wheresetjmpwas called, causingsetjmpto return a second time with the valueval.
Critically, longjmp does not unwind the stack or track
operations that occurred between the call to setjmp and the
call to longjmp; it simply resets the CPU core state to the
point of the snapshot.
Compiler Register Allocation and Clobbering
Compilers optimize code performance by allocating frequently accessed automatic (local) variables to CPU registers rather than reading and writing them to the stack frame in RAM.
When a local variable is modified after setjmp:
- If the compiler placed that variable into a register, the updated value exists only in that register.
- The original value of that register was already recorded in
jmp_bufduring the initialsetjmpinvocation. - When
longjmpexecutes, it overwrites the CPU registers with the values preserved injmp_buf. - As a result, the register holding your local variable is reverted to
its pre-
setjmpstate, discarding any assignments made in the interim.
If the compiler instead stored the variable on the stack frame (which
remains in memory because the function containing setjmp
has not returned), the variable might retain its modified value. Because
register allocation depends entirely on optimization levels
(-O0, -O2, etc.) and compiler heuristics, the
variable's value becomes non-deterministic without proper
qualification.
The C Standard Rule
To account for these architectural realities, the ISO C Standard
(ISO/IEC 9899) explicitly defines this behavior. According to the
standard, the values of automatic local variables in the function
invoking setjmp become indeterminate after a
longjmp if they meet all of the following conditions:
- They are local to the function containing the
setjmpinvocation. - They do not have the
volatilequalifier. - They are changed between the
setjmpinvocation and thelongjmpcall.
Reading such a variable after longjmp returns yields
undefined behavior.
Preventing Clobbering with
volatile
Declaring a variable as volatile informs the compiler
that the variable's value may change outside the normal compiler flow
and must not be cached in a CPU register.
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void trigger_jump(void) {
longjmp(env, 1);
}
int main(void) {
int regular_var = 10;
volatile int volatile_var = 10;
if (setjmp(env) == 0) {
regular_var = 20;
volatile_var = 20;
trigger_jump();
} else {
// regular_var is indeterminate; it may print 10 or 20 depending on optimization.
// volatile_var is guaranteed to retain 20.
printf("regular_var: %d\n", regular_var);
printf("volatile_var: %d\n", volatile_var);
}
return 0;
}Because volatile_var is forced to reside in memory on
the stack, every write goes directly to RAM. When longjmp
restores the CPU registers, the memory location on the stack remains
untouched, allowing the program to safely read the updated value.