我最近在Python(以及JavaScript)中遇到了yield
关键字-我知道这主要用于生成器模式,但语言结构似乎也用于异步函数,这也是我感兴趣的地方。在异步函数中,它可能只是作为语法糖,我知道有其他模式可以达到同样的效果-但我喜欢它-非常喜欢!
我想知道我是否可以在C中做类似的事情(即使是内联汇编)。我遇到了一个使用线程https://github.com/mherrmann/java-generator-functions的Java实现,我可以或多或少地在c中实现,但这不会是一个独立的实现,我的兴趣纯粹是在一个独立的实现。
说到C协同例程(http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html),缺陷之一是不能使用堆栈对象。然而,我仍然可以这样做,因为当前的异步回调实现也不能使用堆栈。然而,问题在于独立的实现-我想不出一种方法来收集所有的寄存器变量,并在没有托管环境的情况下存储它们。
可能有一个解决方案使用setjmp/longjmp
,但我很确定这些不能独立实现。
我个人认为我已经用尽了所有的可能性,所以我要问这个-如果你可以有一个托管实现,你会如何实现它(最好是一些宏魔术)?我有一个相当丑陋的实现,如果没有什么很酷的,我将在稍后发布。
我也不想要c++实现——除非你可以用纯C函数来包装c++。
编辑:一个基本要求是生成器函数必须是可重入的。
忽略特定于语言的术语,您正在寻找的是所谓的"协程"。Simon Tatham想出了一些看起来和行为都很像协同程序的东西,加上一些预处理器的魔法。与的工作方式并不完全相同,但在大多数情况下,它假装是有用的。
详情请参阅此处。
根据您的具体问题,这可能足够,也可能不够。无论如何,这种方法的优点是,它适用于标准C语言;不需要非标准编译器
我将使用setjmp
和longjmp
来回答,因为这些接口是标准的,您可以很容易地找到任何硬件平台的实现。它们是独立的,但是依赖于HW。
struct _yield_state {
jmp_buf buf;
_Bool yielded;
};
#define yieldable static struct _yield_state _state;
if (_state.yielded) longjmp(_state.buf, 1); else {}
#define yield(x) if (setjmp(_state.buf)) { _state.yielded = false; }
else { _state.yielded = true; return x }
int func(int a, int b)
{
yieldable;
if (a > b)
yield(0);
return a + b;
}
您可以在这里找到setjmp
和longjmp
实现的示例。它是纯汇编,只针对底层硬件。
Python中的迭代器遵循以下模式:调用它们(带参数),它们返回一个对象。你反复调用对象的.next()
或.__next__()
方法,它会在迭代器中运行。
我们可以做类似的事情:
typedef struct iterator{
int yield_position; /* Where to jump to */
void *yield_state; /* opaque container for local variables */
void *(*next)(iterator*); /* Function taking "this" argument
returning a pointer to whatever we yielded */
} iterator;
iterator *make_generator(/* arguments? */){
iterator *result = malloc(sizeof(iterator)); /* Caller frees */
result->yield_position = 0;
/* Optionally allocate/initialize yield_state here */
result->next = do_generator;
return result;
}
void *do_generator(iterator *this){
struct whatever *result;
switch(this->yield_position){
case 0:
/* Do something */
this->yield_position = 1;
/* Save local variables to this->yield_state if necessary */
return (void *) result;
case 1:
/* Initialize local variables from this->yield_state */
/* Etc.*/
}
}
void free_generator(iterator *iter){
/* Free iter->yield_state if necessary */
free(iter);
}
由于case标签几乎可以在任何地方使用,因此switch应该能够在必要时跳转到循环的中间。你可能仍然需要重新初始化循环变量等。
它是这样命名的:
iterator *iter = make_generator(/* arguments? */);
struct whatever *foo = iter->next(iter);
/* etc. */
free_generator(iter);
手动传递this
参数很繁琐,所以定义一个宏:
#DEFINE NEXT(iter) ((iter)->next(iter))