如何在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
相关产品推荐
相关产品推荐

