有没有办法在C中实现闭包
我希望这可以工作,但它没有:
#include 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中获得闭包吗?
真的很棒!
使用FFCALL ,
#include #include 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对C称为块的非标准扩展,它的工作方式与闭包类似。
GCC和clang具有块扩展,这基本上是C中的闭包。
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 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
GCC支持内部函数,但不支持闭包。 C ++ 0x将有闭包。 没有我所知道的C版本,当然也没有标准版本,提供了那么棒的版本。
Phoenix是Boost的一部分,它提供了C ++的闭包。
在此页面上,您可以找到有关如何在C中执行闭包的说明:
http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/
我们的想法是需要一个结构体,并且该结构包含函数指针,但作为第一个参数提供给函数。 除了它需要大量的锅炉板代码并且存储器管理当然是一个问题之外,这也起作用并且提供了其他语言闭合的能力和可能性。
使用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不同,它也没有垃圾收集,还有一些其他function可以很容易地用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中的x64调用约定概述以及如何在Windows上的64位应用程序中运行时在程序集中设置函数参数? 以及如何在gcc中实现变量参数中的各种答案和注释? 。
我们能做的一件事就是解决为C提供某种闭包设施的问题,即简化问题。 最好提供80%的解决方案,这对大多数应用程序而言根本没有解决方案。
一个这样的简化是仅支持不返回值的函数,换句话说,声明为void func_name()
。 我们还将放弃函数参数列表的编译时类型检查,因为此方法在运行时构建函数参数列表。 我们放弃的这些事情中的任何一个都是微不足道的,所以问题在于这种关闭C的方法的价值是否超过了我们放弃的价值。
首先,我们定义闭包数据区域。 闭包数据区域表示我们将用于包含闭包所需信息的内存区域。 我能想到的最小数据量是指向要执行的函数的指针,以及作为参数提供给函数的数据的副本。
在这种情况下,我们将提供函数所需的任何捕获的状态数据作为函数的参数。
我们还希望有一些基本的安全防护装置,以便我们能够合理安全地失败。 不幸的是,安全轨道有点弱,我们正在使用一些工作来实现一种forms的闭合。
源代码
以下源代码是使用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
数组的struct的指针,然后取消引用指针,以便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
那么干什么呢?
接下来,我们可以对闭包结构进行修改,以允许我们对函数进行currying。
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;
具有咖喱点的调整和重置的支持function
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