我最近遇到了Python中的yield
关键字(以及JavaScript) - 我知道这主要用于生成器模式,但语言结构似乎也用于异步函数利益所在。在异步函数中,它可能只是作为合成糖,我知道有其他模式可以达到同样的效果 - 但我喜欢它 - 很多!
我想知道我是否可以在C中做类似的事情(即使使用内联汇编)。我遇到了一个使用线程https://github.com/mherrmann/java-generator-functions的Java实现,我可以在C中或多或少地实现它。但是这不是一个独立的实现,我的兴趣纯粹是一个独立的实现。
来到C协同例程(http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html),其中一个缺点是无法使用堆栈对象。但是我仍然可以使用它,因为当前的异步回调实现也不能使用堆栈。然而问题在于独立实现 - 我无法想出一种收集所有寄存器变量并在没有托管环境的情况下存储它们的方法。
可能使用setjmp/longjmp
的解决方案,但我很确定这些解决方案无法实现独立。
所以问题是:是否有可能在独立 C中实现Python yield功能?
我个人认为我已经筋疲力尽了,所以我会问这个问题 - 如果你有一个托管实现,你会如何实现它(最好带一些宏观魔法)?我有一个相当丑陋的实现,如果没有什么好的话,我稍后会发布。
此外,我不想要C ++实现 - 除非您可以使用纯C函数包装C ++。
编辑:一个基本要求是生成器功能必须重新进入。
答案 0 :(得分:5)
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);
}
由于案例标签can be used just about everywhere,交换机应该能够例如如有必要,跳进循环的中间。您可能仍需要重新初始化循环变量等。
它被称为:
iterator *iter = make_generator(/* arguments? */);
struct whatever *foo = iter->next(iter);
/* etc. */
free_generator(iter);
手动传递this
参数变得乏味,因此定义一个宏:
#DEFINE NEXT(iter) ((iter)->next(iter))
答案 1 :(得分:5)
忽略特定于语言的术语,您正在寻找的内容称为" coroutines"。西蒙·塔特姆(Simon Tatham)提出的东西看起来和行为非常像具有一些预处理器魔力的协同程序。它没有完全以相同的方式工作,但它以一种对大多数情况有用的方式假装。
有关详细信息,请参阅here。
根据您的确切问题,这可能已足够,或者可能不是。无论如何,这种方法的优点是它适用于标准C;不需要非标准编译器。
答案 2 :(得分:4)
我将使用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
实施here。它是纯粹的程序集,仅针对底层硬件。