2

这个问题来自C 中 setjmp 和 longjmp 的实际用法以及我问的如何在 c 中的 for 循环中实现协程。

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;
}

我正在研究 C. 的协程实现,并试图查看longjmp.

问题一:

使用后有什么魔力使堆栈routineB活着alloca(2048)?我听说alloca是邪恶的,但为什么它使堆栈看起来像扩展了。我应该这样使用它吗?

输出:

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

问题2:

删除后alloca(2048)。在告诉编译器禁用优化(-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

如果它不是未定义的,如何使代码获得相同的行为?如果是,请忘记 Q2。

4

2 回答 2

2

这是一篇关于使用 setjmp/longjmp/alloca 实现 coros 的文章:https ://fanf.livejournal.com/105413.html 。

这个想法是,为了让 B 在长跳回到 A 时保留它的完整上下文(不仅仅是寄存器(由 setjmp 保留),还有本地的堆栈变量),B 需要它自己的堆栈,或者至少它需要确保 A 所做的任何事情都不会覆盖 B 的变量。

alloca是一种无需深入组装即可实现这一目标的方法。 alloca基本上将 B 在堆栈上比 A 移动得更远,因此除非 A 使用深度递归或任何使其使用超过 2KiB(在这种情况下)的堆栈,A 和 B 将保留它们的堆栈上局部变量分离。

malloc(这种技术很自然地不严格符合 C,如果你在多个'd 堆栈之间使用来回跳转,它会更少。)

于 2018-05-23T08:33:47.717 回答
0

关于第二个问题的解决方案:

放入int r数据段将给出相同的结果,无论是在 GCC 中发布还是调试。

static int r = 0;
于 2018-05-24T01:07:51.303 回答