2

This question comes from Practical usage of setjmp and longjmp in C and How to implement coroutine within for loop in c which I asked.

jmp_buf bufferA, bufferB;

void routineB(); // forward declaration

void routineA()
{
    int r = 0;

    printf("(A1)\n");

    if (setjmp(bufferA) == 0) {
        r++;
        alloca(2048);
        routineB();
    }

    printf("(A2) r=%d\n",r);

    if (setjmp(bufferA) == 0) {
        r++;
        longjmp(bufferB, 1);
    }

    printf("(A3) r=%d\n",r);

    if (setjmp(bufferA) == 0) {
        r++;
        longjmp(bufferB, 1);
    }

    printf("(A4) r=%d\n",r);
}

void routineB()
{
    int r = 0;

    printf("(B1)\n");

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B2) r=%d\n", r);

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B3) r=%d\n", r);

    if (setjmp(bufferB) == 0) {
        r++;
        longjmp(bufferA, 1);
    }

    printf("(B4) r=%d never reach\n", r);
}

int main()
{
    printf("main\n");
    routineA();
    return 0;
}

I am studying about the coroutine implementation by C. and trying to see what happened in the stack after longjmp.

Question 1:

What's the magic makes the stack of routineB alive after using alloca(2048)? I heard alloca is evil but why it makes the stack looks like expanded. Should I use it like this?

Output:

main
(A1)
(B1)
(A2) r=1
(B2) r=1
(A3) r=2
(B3) r=2
(A4) r=3

Question 2:

After removing alloca(2048). it gives different result after tell complier disable optimization(-O2).

-O0

main
(A1)
(B1)
(A2) r=1
(B2) r=6356584
(A3) r=2
(B3) r=6356584
(A4) r=3

-O2

main
(A1)
(B1)
(A2) r=1
(B2) r=0
(A3) r=1
(B3) r=0
(A4) r=1

if it's not undefined, how to make the code get the same behaviour? if it's, Please forget Q2.

JustWe
  • 4,250
  • 3
  • 39
  • 90
  • Beware of [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) – Basile Starynkevitch May 23 '18 at 08:13
  • 1
    Like I explained [here](https://stackoverflow.com/questions/50481755/how-to-implement-coroutine-within-for-loop-in-c/50482435#50482435), the code invokes undefined behavior. You should not be doing such jumps between functions. – Ajay Brahmakshatriya May 23 '18 at 08:14
  • @AjayBrahmakshatriya So I shall simply consider `longjmp` as a speical `return`. – JustWe May 23 '18 at 08:21
  • 1
    @Jiu, `longjmp` is not always a return. It terminates the function only if the corresponding `setjmp` was in a function earlier in the set of nested calls. If the `setjmp` is in the same function, it is like a normal `goto`. Also the `setjmp` could be many steps down the nesting. So it is not like `return` at all. – Ajay Brahmakshatriya May 23 '18 at 08:24
  • @AjayBrahmakshatriya Thanks! The Best summary of using `longjmp` & `setjmp`. – JustWe May 23 '18 at 08:29
  • alloca is not nearly as evil as setjmp/longjmp. – Lundin May 23 '18 at 11:05

1 Answers1

2

Here's an article about implementing coros with setjmp/longjmp/alloca: https://fanf.livejournal.com/105413.html .

The idea is that in order for B to preserve it's full context (not just registers (preserved by setjmp) but also local, on-stack variables) when long-jumping back to A, B needs its own stack or at least it needs to make sure whatever A does won't overwrite B's variables.

alloca is a way to achieve that without delving into assembly. alloca will basically move B much further on the stack than A is so that unless A uses deep recursion or anything that'd make it use more than 2KiB (in this case) of its stack, A and B will keep their on-stack local variables separate.

(This technique is quite naturally not strictly conforming C, and it'd be even less so if you used back-and-forth jumps between multiple malloc'd stacks.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • So the `2048` split stack makes A & B separately? – JustWe May 23 '18 at 08:46
  • 1
    @Jiu alloca(2048) moves the stack pointer forward 2048 bytes, which means that routineB will start 2048 later and when B longjmp-returns to A, A will be able to use 2048 bytes of stack before B's on-stack state is compromised. – Petr Skocik May 23 '18 at 08:50