有没有办法在C中实现闭包

时间:2010-12-09 00:21:45

标签: c closures

我希望这可行,但事实并非如此:

#include <stdio.h>

typedef struct closure_s {
  void (*incrementer) ();
  void (*emitter) ();
} closure;

closure emit(int in) {

  void incrementer() {
    in++;
  }

  void emitter() {
    printf("%d\n", in);
  }

  return (closure) {
    incrementer,
    emitter
  };
}

main() {
  closure test[] = {
    emit(10),
    emit(20)
  };

  test[0] . incrementer();
  test[1] . incrementer();

  test[0] . emitter();
  test[1] . emitter();
}

实际上 编译并且对1个实例起作用...但是第二个实例失败了。知道如何在C中获得闭包吗?

真的很棒!

7 个答案:

答案 0 :(得分:19)

使用FFCALL

#include <callback.h>
#include <stdio.h>
static void incrementer_(int *in) {
    ++*in;
}
static void emitter_(int *in) {
    printf("%d\n", *in);
}
int main() {
    int in1 = 10, in2 = 20;
    int (*incrementer1)() = alloc_callback(&incrememnter_, &in1);
    int (*emitter1)() = alloc_callback(&emitter_, &in1);
    int (*incrementer2)() = alloc_callback(&incrememnter_, &in2);
    int (*emitter2)() = alloc_callback(&emitter_, &in2);
    incrementer1();
    incrementer2();
    emitter1();
    emitter2();
    free_callback(incrementer1);
    free_callback(incrementer2);
    free_callback(emitter1);
    free_callback(emitter2);
}

但通常在C中你最终会将额外的参数传递给假闭包。


Apple有一个名为blocks的非标准扩展名,其功能与闭包类似。

答案 1 :(得分:7)

GCC和clang具有块扩展,这基本上是C中的闭包。

答案 2 :(得分:4)

ANSI C不支持闭包以及嵌套函数。解决方法是使用简单的“struct”。

两个数字相加的简单示例闭包。

// Structure for keep pointer for function and first parameter
typedef struct _closure{
    int x;
    char* (*call)(struct _closure *str, int y);
} closure;


// An function return a result call a closure as string
char *
sumY(closure *_closure, int y) {
    char *msg = calloc(20, sizeof(char));
    int sum = _closure->x + y;
    sprintf(msg, "%d + %d = %d", _closure->x, y, sum);
    return msg;
}


// An function return a closure for sum two numbers
closure *
sumX(int x) {
    closure *func = (closure*)malloc(sizeof(closure));
    func->x = x;
    func->call = sumY;
    return func;
}

用法:

int main (int argv, char **argc)
{

    closure *sumBy10 = sumX(10);
    puts(sumBy10->call(sumBy10, 1));
    puts(sumBy10->call(sumBy10, 3));
    puts(sumBy10->call(sumBy10, 2));
    puts(sumBy10->call(sumBy10, 4));
    puts(sumBy10->call(sumBy10, 5));
}

结果:

10 + 1 = 11
10 + 3 = 13
10 + 2 = 12
10 + 4 = 14
10 + 5 = 15

在C ++ 11上,它将通过使用lambda表达式来实现。

#include <iostream>
int main (int argv, char **argc)
{
    int x = 10;
    auto sumBy10 = [x] (int y) {
        std::cout << x << " + " << y << " = " << x + y << std::endl;
    };
    sumBy10(1);
    sumBy10(2);
    sumBy10(3);
    sumBy10(4);
    sumBy10(5);
}

使用标志-std = c ++ 11编译后的结果。

10 + 1 = 11
10 + 2 = 12
10 + 3 = 13
10 + 4 = 14
10 + 5 = 15

答案 3 :(得分:2)

GCC支持内部函数,但不支持闭包。 C ++ 0x将有闭包。没有我所知道的C版本,当然也没有标准版本,提供了那么棒的。

Phoenix是Boost的一部分,它提供了C ++中的闭包。

答案 4 :(得分:1)

在此页面上,您可以找到有关如何在C中执行闭包的说明:

http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/

这个想法是需要一个结构,并且该结构包含函数指针,但作为第一个参数提供给函数。除了它需要大量的锅炉板代码并且内存管理当然是一个问题之外,这也起到了作用,并提供了其他语言封闭的能力和可能性。

答案 5 :(得分:1)

使用JavaScript示例的闭包的工作定义

闭包是一种对象,它包含某个函数的指针或引用,以及要与函数所需的数据实例一起执行的函数。

来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures的JavaScript中的示例是

function makeAdder(x) {
  return function(y) { // create the adder function and return it along with
    return x + y;      // the captured data needed to generate its return value
  };
}

然后可以像:

一样使用
var add5 = makeAdder(5);  // create an adder function which adds 5 to its argument

console.log(add5(2));  // displays a value of 2 + 5 or 7

克服C的一些障碍

C编程语言是一种静态类型语言,与JavaScript不同,它也没有垃圾收集,还有一些其他功能可以很容易地用JavaScript或其他语言进行闭包,并且内部支持闭包。

标准C中闭包的一个大障碍是JavaScript示例中缺少对构造类型的语言支持,其中闭包不仅包括函数,还包括创建闭包时捕获的数据副本,一种保存状态的方法,可以在执行闭包以及调用闭包函数时提供的任何其他参数时使用。

然而,C确实有一些基本的构建块,可以提供创建一种闭包的工具。一些困难是(1)内存管理是程序员的职责,没有垃圾收集,(2)函数和数据是分开的,没有类或类类型的机制,(3)静态类型因此没有运行时发现数据类型或者数据大小,以及(4)在创建闭包时捕获状态数据的语言设施不佳。

使用C创建闭包工具的一件事是void *指针并使用unsigned char作为一种通用内存类型,然后通过转换将其转换为其他类型。

标准C的实现和一点点的拉伸

注意: 以下示例依赖于基于堆栈的参数传递约定,与大多数x86 32位编译器一样。大多数编译器还允许指定调用约定而不是基于堆栈的参数传递,例如Visual Studio的__fastcall修饰符。 x64和64位Visual Studio的默认设置是默认使用__fastcall约定,以便函数参数在寄存器中传递,而不是在堆栈中传递。请参阅Microsoft MSDN中的Overview of x64 Calling Conventions以及How to set function arguments in assembly during runtime in a 64bit application on Windows?以及How are variable arguments implemented in gcc?中的各种答案和评论。

我们能做的一件事就是解决为C提供某种闭包设施的问题,即简化问题。最好提供80%的解决方案,这对大多数应用程序都有用,而不是完全没有解决方案。

一个这样的简化是仅支持不返回值的函数,换句话说,声明为void func_name()的函数。我们还将放弃函数参数列表的编译时类型检查,因为此方法在运行时构建函数参数列表。我们放弃的这些事情中的任何一个都不是微不足道的,所以问题是这种关闭C的方法的价值是否超过了我们放弃的价值。

首先让我们定义闭包数据区域。闭包数据区域表示我们将用于包含闭包所需信息的内存区域。我能想到的最小数据量是指向要执行的函数的指针,以及作为参数提供给函数的数据的副本。

在这种情况下,我们将提供函数所需的任何捕获状态数据作为函数的参数。

我们还希望有一些基本的安全防护装置,以便我们能够合理安全地失败。不幸的是,安全轨道有点弱,我们正在使用一些工作来实现一种形式的闭合。

源代码

以下源代码是使用Visual Studio 2017 Community Edition在.c C源文件中开发的。

数据区是一个结构,包含一些管理数据,指向该函数的指针和一个开放式数据区。

typedef struct {
    size_t  nBytes;    // current number of bytes of data
    size_t  nSize;     // maximum size of the data area
    void(*pf)();       // pointer to the function to invoke
    unsigned char args[1];   // beginning of the data area for function arguments
} ClosureStruct;

接下来,我们创建一个初始化闭包数据区的函数。

ClosureStruct * beginClosure(void(*pf)(), int nSize, void *pArea)
{
    ClosureStruct *p = pArea;

    if (p) {
        p->nBytes = 0;      // number of bytes of the data area in use
        p->nSize = nSize - sizeof(ClosureStruct);   // max size of the data area
        p->pf = pf;         // pointer to the function to invoke
    }

    return p;
}

此函数旨在接受指向数据区域的指针,该指针可灵活地确定函数用户如何管理内存。它们可以在堆栈或静态内存上使用一些内存,也可以通过malloc()函数使用堆内存。

unsigned char closure_area[512];
ClosureStruct *p = beginClosure (xFunc, 512, closure_area);

ClosureStruct *p = beginClosure (xFunc, 512, malloc(512));
//  do things with the closure
free (p);  // free the malloced memory.

接下来,我们提供一个函数,允许我们向闭包添加数据和参数。此函数的目的是构建闭包数据,以便在调用闭包函数时,将为闭包函数提供完成其工作所需的任何数据。

ClosureStruct * pushDataClosure(ClosureStruct *p, size_t size, ...)
{
    if (p && p->nBytes + size < p->nSize) {
        va_list jj;

        va_start(jj, size);    // get the address of the first argument

        memcpy(p->args + p->nBytes, jj, size);  // copy the specified size to the closure memory area.
        p->nBytes += size;     // keep up with how many total bytes we have copied
        va_end(jj);
    }

    return p;
}

为了使这个使用起来更简单,我们提供一个包装宏,它通常很方便,但由于它是C ++文本操作,因此有限制。

#define PUSHDATA(cs,d) pushDataClosure((cs),sizeof(d),(d))

因此我们可以使用类似以下源代码的内容:

unsigned char closurearea[256];
int  iValue = 34;

ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, closurearea), iValue);
dd = PUSHDATA(dd, 68);
execClosure(dd);

调用Closure:execClosure()函数

最后一部分是用execClosure()函数执行闭包函数及其数据。我们在这个函数中做的是在调用函数时将闭包数据结构中提供的参数列表复制到堆栈上。

我们所做的是将闭包数据的args区域转换为指向包含unsigned char数组的结构的指针,然后取消引用指针,以便C编译器将参数的副本放入堆栈之前它在闭包中调用函数。

为了更容易创建execClosure()函数,我们将创建一个宏,以便轻松创建我们需要的各种大小的结构。

// helper macro to reduce type and reduce chance of typing errors.

#define CLOSEURESIZE(p,n)  if ((p)->nBytes < (n)) { \
struct {\
unsigned char x[n];\
} *px = (void *)p->args;\
p->pf(*px);\
}

然后我们使用这个宏来创建一系列测试来确定如何调用闭包函数。这里选择的尺寸可能需要针对特定​​应用进行调整。这些大小是任意的,因为闭包数据很少具有相同的大小,所以这不能有效地使用堆栈空间。并且可能存在比我们允许的更多关闭数据。

// execute a closure by calling the function through the function pointer
// provided along with the created list of arguments.
ClosureStruct * execClosure(ClosureStruct *p)
{
    if (p) {
        // the following structs are used to allocate a specified size of
        // memory on the stack which is then filled with a copy of the
        // function argument list provided in the closure data.
        CLOSEURESIZE(p,64)
        else CLOSEURESIZE(p, 128)
        else CLOSEURESIZE(p, 256)
        else CLOSEURESIZE(p, 512)
        else CLOSEURESIZE(p, 1024)
        else CLOSEURESIZE(p, 1536)
        else CLOSEURESIZE(p, 2048)
    }

    return p;
}

我们返回指向闭包的指针,以使其易于使用。

使用库开发的示例

我们可以使用以下内容。首先是几个不太重要的示例功能。

int zFunc(int i, int j, int k)
{
    printf("zFunc i = %d, j = %d, k = %d\n", i, j, k);
    return i + j + k;
}

typedef struct { char xx[24]; } thing1;

int z2func(thing1 a, int i)
{
    printf("i = %d, %s\n", i, a.xx);
    return 0;
}

接下来,我们构建闭包并执行它们。

{
    unsigned char closurearea[256];
    thing1 xpxp = { "1234567890123" };
    thing1 *ypyp = &xpxp;
    int  iValue = 45;

    ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
    free(execClosure(PUSHDATA(dd, iValue)));

    dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
    dd = PUSHDATA(dd, 68);
    execClosure(dd);

    dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
    dd = PUSHDATA(dd, 145);
    dd = PUSHDATA(dd, 185);
    execClosure(dd);
}

其中输出

i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185

干什么呢?

接下来,我们可以对闭包结构进行修改,以允许我们对函数进行求解。

typedef struct {
    size_t  nBytes;    // current number of bytes of data
    size_t  nSize;     // maximum size of the data area
    size_t  nCurry;    // last saved nBytes for curry and additional arguments
    void(*pf)();       // pointer to the function to invoke
    unsigned char args[1];   // beginning of the data area for function arguments
} ClosureStruct;

具有用于咖喱点的调整和重置的支持功能

ClosureStruct *curryClosure(ClosureStruct *p)
{
    p->nCurry = p->nBytes;
    return p;
}
ClosureStruct *resetCurryClosure(ClosureStruct *p)
{
    p->nBytes = p->nCurry;
    return p;
}

测试它的源代码可能是:

{
    unsigned char closurearea[256];
    thing1 xpxp = { "1234567890123" };
    thing1 *ypyp = &xpxp;
    int  iValue = 45;

    ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, malloc(256)), xpxp);
    free(execClosure(PUSHDATA(dd, iValue)));
    dd = PUSHDATA(beginClosure(z2func, 256, closurearea), *ypyp);
    dd = PUSHDATA(dd, 68);
    execClosure(dd);
    dd = PUSHDATA(beginClosure(zFunc, 256, closurearea), iValue);
    dd = PUSHDATA(dd, 145);
    dd = curryClosure(dd);
    dd = resetCurryClosure(execClosure(PUSHDATA(dd, 185)));
    dd = resetCurryClosure(execClosure(PUSHDATA(dd, 295)));
}

输出

i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
zFunc i = 45, j = 145, k = 295

答案 6 :(得分:1)

答案

#include <stdio.h>
#include <stdlib.h>

/*
File Conventions
----------------
alignment: similar statements only
           int    a = 10;
           int* omg = {120, 5};
functions: dofunction(a, b, c);
macros:    _do_macro(a, b, c);
variables: int dovariable=10;
*/


////Macros

#define _assert(got, expected, teardownmacro) \
  do { \
    if((got)!=(expected)) { \
      fprintf(stderr, "line %i: ", __LINE__); \
      fprintf(stderr, "%i != %i\n", (got), (expected)); \
      teardownmacro; \
      return EXIT_FAILURE; \
    } \
  } while(0);


////Internal Helpers

static void istarted() {
  fprintf(stderr, "Start tests\n");
}

static void iended() {
  fprintf(stderr, "End tests\n");
}

////Tests

int main(void)
{
  ///Environment

  int  localvar = 0;
  int* localptr = NULL;


  ///Closures

#define _setup_test(mvar, msize) \
  do { \
    localptr=calloc((msize), sizeof(int)); \
    localvar=(mvar); \
  } while(0);

#define _teardown_test() \
  do { \
    free(localptr); \
    localptr=NULL; \
  } while(0);

  
  ///Tests
  
  istarted();

  _setup_test(10, 2);
  _assert(localvar, 10, _teardown_test());
  _teardown_test();

  _setup_test(100, 5);
  _assert(localvar, 100, _teardown_test());
  _teardown_test();

  iended();

  return EXIT_SUCCESS;
}

上下文

我很好奇其他人如何用C语言做到这一点。当我没有看到这个答案时,我并不感到完全惊讶。 警告:此答案不适用于初学者。

我生活在Unix风格的思想上:我的许多个人程序和库都很小,并且一件事做得很好。在这种情况下,作为“封闭件”的宏要安全得多。我认为所有组织和特定于可读性的约定都非常重要,因此以后我们可以读取代码,宏看起来像宏,函数看起来像函数。为了澄清这些个人约定,而不是从字面上澄清,仅指定了一些约定,并遵循了这些约定以区别不同的语言构造(宏和函数)。无论如何我们都应该这样做。

不要害怕宏。 有意义时:使用它们。高级部分是时间。我的示例是时间的示例。它们非常强大,而且没有那么恐怖。


正在漫游

我有时会使用其他语言的适当的闭包/ lambda在函数中反复执行一组表达式。这是一个上下文相关的私人帮助器功能。不管其正确定义如何,这都是闭包可以执行的操作。它可以帮助我减少编写代码。这样做的另一个好处是,您无需引用结构即可知道如何使用它或了解它在做什么。其他答案没有这个好处,而且,如果不是很明显,我会非常重视可读性。我努力寻求简单易懂的解决方案。这次,我编写了一个iOS应用程序,它非常有趣,而且我可以轻松实现。然后,我用5行代码在bash中编写了相同的“ app”并加以诅咒。

还有嵌入式系统。