You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C语言for循环中实现协程?解决计数器未更新问题

问题分析:为什么计数器i始终不更新?

你的问题核心在于**setjmp/longjmp的上下文保存特性**和C语言中自动变量(栈上局部变量)的生命周期与优化行为:

  • 当你在a()里调用b(),或b()里调用a()时,新的函数栈帧会覆盖之前的栈帧内容。当通过longjmp跳回之前的setjmp点时,原来的局部变量i所在的栈空间可能已经被破坏;再加上编译器可能会把i优化到寄存器中,导致longjmp后i的值无法正确恢复。
  • 根据C标准,longjmp返回后,非volatile的自动变量的值是未定义的,除非该变量在setjmp之后没有被修改过。你的i在循环中不断被修改,完全符合这个未定义行为的场景。
解决方案:让协程状态持久化

有两种常用的修复方式,推荐第二种更规范的协程实现思路:

方式1:将局部变量声明为volatile

最简单的临时修复是把i标记为volatile,强制编译器将其存储在内存中而非寄存器,这样longjmp回来时能正确读取到最新值:

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

jmp_buf a_buf;
jmp_buf b_buf;

void a() {
    printf("entering a\n");
    volatile int i; // 加volatile修饰
    for(i = 0; i < 3; i++){
        if(setjmp(a_buf) == 0) {
            printf("A step %d\n", i);
            b();
        } else {
            longjmp(b_buf, 1);
        }
    }
    printf("returning from a\n");
}

void b() {
    printf("entering b\n");
    volatile int i; // 加volatile修饰
    for(i = 0; i < 5; i++){
        if(setjmp(b_buf) == 0) {
            printf("B step %d\n", i);
            a();
        } else {
            longjmp(a_buf, 1);
        }
    }
    printf("returning from b\n");
}

int main() {
    a();
    return 0;
}

不过这种方式只是临时解决,本质上依赖栈帧的侥幸存活,存在栈溢出风险,并非真正的协程实现思路。

方式2:用独立状态存储实现真正的协程切换

更规范的做法是把每个协程的状态(计数器、上下文)从栈上移到全局/堆内存中,避免递归调用覆盖栈帧。我们可以用结构体封装协程的上下文和运行状态:

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

// 定义协程状态结构体
typedef struct {
    jmp_buf buf;
    int step; // 保存当前执行到的步骤,替代局部变量i
    int is_running; // 标记协程是否还在运行
} Coroutine;

Coroutine coro_a, coro_b;

void a_coroutine() {
    printf("entering a\n");
    for(coro_a.step = 0; coro_a.step < 3; coro_a.step++){
        printf("A step %d\n", coro_a.step);
        // 切换到b协程
        if(setjmp(coro_a.buf) == 0) {
            longjmp(coro_b.buf, 1);
        }
    }
    coro_a.is_running = 0;
    printf("returning from a\n");
    // 切换回b协程(如果b还在运行)
    if(coro_b.is_running) {
        longjmp(coro_b.buf, 1);
    }
}

void b_coroutine() {
    printf("entering b\n");
    for(coro_b.step = 0; coro_b.step < 5; coro_b.step++){
        printf("B step %d\n", coro_b.step);
        // 切换到a协程
        if(setjmp(coro_b.buf) == 0) {
            longjmp(coro_a.buf, 1);
        }
    }
    coro_b.is_running = 0;
    printf("returning from b\n");
    // 切换回a协程(如果a还在运行)
    if(coro_a.is_running) {
        longjmp(coro_a.buf, 1);
    }
}

int main() {
    // 初始化协程状态
    coro_a.is_running = 1;
    coro_b.is_running = 1;

    // 启动a协程,第一次setjmp保存上下文
    if(setjmp(coro_a.buf) == 0) {
        a_coroutine();
    }

    // 启动b协程,第一次setjmp保存上下文
    if(setjmp(coro_b.buf) == 0) {
        b_coroutine();
    }

    // 循环切换协程直到两者都结束
    while(coro_a.is_running || coro_b.is_running) {
        if(coro_a.is_running) {
            longjmp(coro_a.buf, 1);
        }
        if(coro_b.is_running) {
            longjmp(coro_b.buf, 1);
        }
    }

    return 0;
}

这个实现的优势:

  • 协程的状态(step)存储在全局结构体中,不再依赖栈帧,避免了栈溢出和变量值被覆盖的问题。
  • 明确标记协程的运行状态,能正确处理其中一个协程先结束的场景。
  • 切换逻辑更清晰,符合协程“暂停-恢复”的核心思想,而非依赖递归调用。
运行效果验证

修改后的代码会按照你期望的顺序输出:

entering a
A step 0
entering b
B step 0
A step 1
B step 1
A step 2
B step 2
B step 3
B step 4
returning from b
returning from a

内容的提问来源于stack exchange,提问作者JustWe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:42:38