ccoroutinesetjmpalloca

What's the function of alloca() with setjmp?


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.


Solution

  • 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.)