以前用C语言中的switch case实现过一个简单的协程,并应用到了实际的项目里。文章在这里,C语言实现协程,最近了解到C99中,goto语句可以跳转到一个变量里,变量保存的是label的地址。于是瞬间想到,这个可以替换协程实现中的switch case的机制。
首先,说明一下什么是goto到标签地址。
int main()
{
static void* p = &&label;
goto *p;
printf("before label\n");
label:
printf("after label\n");
return 0;
}
- &&label是语法,&&就是获得label的地址,并不是取地址在取地址。
- goto *p 就是跳转到p指针,所指向的地址。
- 如果p指向了的是不正确的地址,程序会运行时崩溃。
- label地址需要用void*指针才存放。
那么,现在我们看怎么用这个机制来实现协程。之前需要阅读,
C语言实现协程,重复内容不再叙述。只贴出改造的的部分。
/**
* Construct goto label with line number
*/
#define _ACoroutineLabel(line) label##line
#define ACoroutineLabel(line) _ACoroutineLabel(line)
#define ACoroutineBegin() \
if (coroutine->step != NULL) \
{ \
goto *coroutine->step; \
} \
coroutine->state = coroutine_state_running \
#define ACoroutineEnd() \
coroutine->state = coroutine_state_finish
#define ACoroutineYieldFrame(waitFrameCount) \
coroutine->waitValue = waitFrameCount; \
coroutine->curWaitValue = 0.0f; \
coroutine->waitType = coroutine_wait_frame; \
coroutine->step = &&ACoroutineLabel(__LINE__); \
return; \
ACoroutineLabel(__LINE__):
#define ACoroutineYieldSecond(waitSecond) \
coroutine->waitValue = waitSecond; \
coroutine->curWaitValue = 0.0f; \
coroutine->waitType = coroutine_wait_second; \
coroutine->step = &&ACoroutineLabel(__LINE__); \
return; \
ACoroutineLabel(__LINE__):
#define ACoroutineYieldCoroutine(waitCoroutine) \
coroutine->waitValue = 0.0f; \
coroutine->curWaitValue = 0.0f; \
coroutine->waitType = coroutine_wait_coroutine; \
AArrayListAdd((waitCoroutine)->waits, coroutine); \
coroutine->step = &&ACoroutineLabel(__LINE__); \
return; \
ACoroutineLabel(__LINE__):
- 首先,我们把coroutine->step改成void*类型,存储label标签地址。
- ACoroutineLabel 是为了展开__Line__定义,构造label + 行号的标签名称。
- 接下来就是,在每次进入协程函数的时候,直接goto到step存储的标签上。
- 没有了switch case,就避免了switch嵌套的可能性错误。并且提升了效率。
- 协程函数用使用的,中断宏定义,就是退出+打标签,下次goto到标签处继续运行。
最后,看看使用的样子。
static void LoadingRun(Coroutine* coroutine)
{
static int progress = 0;
//--------------------------------------------------------------------------------------------------
ACoroutineBegin();
for (; progress < progressSize; progress++)
{
ACoroutineYieldFrame(0);
}
ACoroutineYieldSecond(1.0f);
ACoroutineEnd();
}
static void OnReady()
{
ACoroutine->StartCoroutine(LoadingRun);
}