Understanding unexpected loop counter behavior with setjmplongjmp in C - Stack Overflow

admin2025-04-25  2

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.

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.

Share Improve this question edited Jan 15 at 7:51 Jabberwocky 51k18 gold badges68 silver badges127 bronze badges asked Jan 15 at 7:43 JohnJohn 131 silver badge2 bronze badges 2
  • When I compile with GCC 14.2.0 with lots of warnings set (gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes sjlj83.c), I get the message: sjlj83.c: In function ‘routine_a’: —— sjlj83.c:8:14: error: variable ‘i’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] —— 8 | for (int i = 0; i < 10; ++i) —— and similarly for routine_b(). I changed the functions and file-scope variables so they were all static (except main() of course), and I used (void) instead of () for the function parameter lists. – Jonathan Leffler Commented Jan 15 at 15:25
  • The rules about setjmp() and longjmp() in the standard (C11 §7.13 Non-local jumps <setjmp.h> are quite stringent. I note that Clang doesn't have a -Wclobbered option under that name and doesn't report the problem that GCC does report. – Jonathan Leffler Commented Jan 15 at 15:25
Add a comment  | 

3 Answers 3

Reset to default 3

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.

As soon as you leave either function, the local variables go out of scope and you can't jump in there again hoping to find the variables magically preserved.

To illustrate this with gcc 14.2 x86 -O3:

#include <stdio.h>
#include <setjmp.h>

jmp_buf routineA, routineB;

int* last_i;

void routine_a()
{
    int i;
    for (i = 0; i < 10; ++i)
    {
        printf("A stored at address %p: %d\n", &i, i);
        if (!setjmp(routineA)) {
            if (i == 0) { last_i=&i; return; }
            longjmp(routineB, 1);
        }
    }
    last_i=&i;
}

void routine_b()
{
    int i;
    for (i = 0; i < 10; ++i)
    {
        printf("B stored at address %p: %d\n", &i, i);
        if (!setjmp(routineB))  longjmp(routineA, 1);
    }
    last_i=&i;
}

int main()
{
    routine_a();
    printf("A::i is now dead. The memory location might contain %d still.\n", *last_i);
    routine_b();
    printf("B::i is now dead. The memory location might contain %d still.\n", *last_i);
    return 0;
}

Output:

A stored at address 0x7ffc26a56dec: 0
A::i is now dead. The memory location might contain 0 still.
B stored at address 0x7ffc26a56dec: 0
A stored at address 0x7ffc26a56dec: 1
B stored at address 0x7ffc26a56dec: 2
A stored at address 0x7ffc26a56dec: 3
B stored at address 0x7ffc26a56dec: 4
A stored at address 0x7ffc26a56dec: 5
B stored at address 0x7ffc26a56dec: 6
A stored at address 0x7ffc26a56dec: 7
B stored at address 0x7ffc26a56dec: 8
A stored at address 0x7ffc26a56dec: 9
B::i is now dead. The memory location might contain 10 still.

So as you can a see, the i in A dies early on and the i in B happens to inherit its place in memory, hence the values. This is wildly undefined behavior because of the access to automatic storage duration variables after jumping around, but also since the function has finished executing.

If you use static int i in both functions you might get the expected behavior but there are no guarantees.

setjmp/longjmp are surrounded by lots of undefined behavior and general best practice is to avoid them like the plague.

It looks like you are trying to create co-routines (user-space threads).

You are effectively saving and restoring the instruction pointer, but that's not enough. Each thread needs its own call stack and local variables ("stack"). Your two "threads" are clobbering each others local variables since they share the same stack.

To addresses this, you can the use ucontext "library" instead of long jumps.

转载请注明原文地址:http://anycun.com/QandA/1745594779a90933.html