是否有可能在独立的C语言中实现Python yield函数?



我最近在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中实现Python yield功能?

我个人认为我已经用尽了所有的可能性,所以我要问这个-如果你可以有一个托管实现,你会如何实现它(最好是一些宏魔术)?我有一个相当丑陋的实现,如果没有什么很酷的,我将在稍后发布。

我也不想要c++实现——除非你可以用纯C函数来包装c++。

编辑:一个基本要求是生成器函数必须是可重入的。

忽略特定于语言的术语,您正在寻找的是所谓的"协程"。Simon Tatham想出了一些看起来和行为都很像协同程序的东西,加上一些预处理器的魔法。的工作方式并不完全相同,但在大多数情况下,它假装是有用的。

详情请参阅此处。

根据您的具体问题,这可能足够,也可能不够。无论如何,这种方法的优点是,它适用于标准C语言;不需要非标准编译器

我将使用setjmplongjmp来回答,因为这些接口是标准的,您可以很容易地找到任何硬件平台的实现。它们是独立的,但是依赖于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;
}

您可以在这里找到setjmplongjmp实现的示例。它是纯汇编,只针对底层硬件。

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))

最新更新