又刷到个奇技淫巧,看完两眼一黑了

https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

switch 是能跳转到任意控制流内部的,利用这个特性,可以在 C 中实现一个 generator

朴素的 generator 需要实现一个显式的状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int func() {
static int state = 0, i = 0;
switch (state) {
case 0: goto start;
case 1: goto label1;
case 2: goto label2;
}
start:
state = 1;
return 0;
label1:
for (i = 0; i < 10; i++) {
state = 2;
return i;
label2:;
}
return -1;
}

switch 中直接跳转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int func() {
static int state = 0, i = 0;
switch (state) {
case 0:
state = 1;
return 0;
case 1:
for (i = 0; i < 10; i++) {
state = 2;
return i;
case 2:;
}
}
return -1;
}

这样手动地设置 state 就像在不停的使用 goto 语句一样,重复劳动的同时还容易写错,怎么自动设置 state

使用 __LINE__

1
2
3
4
5
6
7
8
9
10
11
int func() {
static int state = 0, i = 0;
switch (state) {
case 0:
case __LINE__: state = __LINE__; return 1;
for (i = 0; i < 10; i++) {
case __LINE__: state = __LINE__; return i;
}
}
return -1;
}

用宏定义简化一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define co_begin             \
static int _co_line = 0; \
switch (_co_line) { \
case 0:;
#define co_end(z) \
} \
return (z)
#define co_end_v \
} \
return

#define co_yield(z) \
do { \
_co_line = __LINE__; \
return (z); \
case __LINE__:; \
} while (0)
1
2
3
4
5
6
7
8
9
int func() {
co_begin;
co_yield(0);
static int i = 0;
for (i = 0; i < 10; i++) {
co_yield(i);
}
co_end(-1);
}

这样将显式的状态机改造成了隐式的,几乎不影响阅读

只需要将普通函数的 return 改成 co_yield 就能将其变为一个 generator
(还需要保存上下文到 static 变量


多线程当中不能这样简单地转换,还要将 static 变量改成一个 context 结构体,每个线程传入自己的 context。这样也能方便的实现可重用,传入空 context 即从头开始执行函数。