I have a piece of code that uses setjmp/longjmp for control flow between two functions. I noticed some unexpected behavior where the loop counter seems to skip values. Here's a minimal example that demonstrates the issue:
#include <stdio.h>
#include <setjmp.h>
jmp_buf routineA, routineB;
void routine_a()
{
for (int i = 0; i < 10; ++i)
{
printf("A %d\n", i);
if (!setjmp(routineA)) {
if (i == 0)
return;
longjmp(routineB, 1);
}
}
}
void routine_b()
{
for (int i = 0; i < 10; ++i)
{
printf("B %d\n", i);
if (!setjmp(routineB))
longjmp(routineA, 1);
}
}
int main()
{
routine_a();
routine_b();
return 0;
}
The output of this code is:
A 0
B 0
A 1
B 2
A 3
B 4
A 5
B 6
A 7
B 8
A 9
I'm confused about why the numbers are skipping. For example, when i=1 in routine_a, it jumps to routine_b, but when control returns to routine_a, the next value printed is 3, skipping 2 entirely. I understand that longjmp breaks the normal control flow, but I don't understand:
I would appreciate any insights into what's happening under the hood.
I tried to ask AI(chatgpt claude), but still couldn't find the answer.
Is this behavior defined by the C standard or implementation-dependent?
The behavior of your program is not defined by the C standard.
C 2024 7.13.3.1 says, of the invocation of longjmp
:
… The longjmp function restores the environment saved by the most recent invocation of the
setjmp
macro in the same invocation of the program with the correspondingjmp_buf
argument. If … the function containing the invocation of thesetjmp
macro has terminated execution in the interim,… the behavior is undefined.
All prior C standards have similar wording. So, when a function calls setjmp
and then returns, the behavior of later passing the associated jmp_buf
object to longjmp
is not defined by the C standard.
Why exactly are values being skipped?
Most commonly, setjmp
and longjmp
implementations rely on the stack to preserve program state. jmp_buf
is an object of fixed size, so it is impossible in general to store all of a function’s variables in it. Primarily, jmp_buf
holds processor registers and other processor state, including the value of the stack pointer. When longjmp
is invoked, restoring the value of the stack pointer, along with other processor state, makes the original stack frame active again if it is still present on the stack. The stack frame contains the values of the function’s variables, unless optimization put them in processor registers.
What happens to the loop counter when longjmp is called?
What is likely happening in your specific program is:
routine_a
is called. It uses stack space for i
and initializes it to 0. routine_a
returns.routine_b
is called. It uses the same stack space for its i
and initializes it to 0. routine_b
invokes longjmp
, which transfers control to routine_a
.routine_a
continues execution through the ++i
portion of its loop, which changes the value of i
(set to 0 in routine_b
) to 1. It prints “1”. Then routine_a
invokes longjmp
, which transfers control to routine_b
.routine_b
continues execution through its ++i
, which changes the value of i
(set to 1 in routine_a
) to 2. It prints “2”. Then routine_b
invokes longjmp
, which transfers control to routine_a
.routine_a
continues execution through its ++i
, which changes i
from 2 to 3. It prints “3”.i
.