Search code examples
csetjmp

Understanding unexpected loop counter behavior with setjmp/longjmp in C


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:

  1. Why exactly are values being skipped?
  2. What happens to the loop counter when longjmp is called?
  3. Is this behavior defined by the C standard or implementation-dependent?

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.


Solution

  • 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 corresponding jmp_buf argument. If … the function containing the invocation of the setjmp 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”.
    • Execution continues in this pattern, with each routine incrementing the value of the shared i.