C语言 有没有办法在 C 中实现闭包
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4393716/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Is there a a way to achieve closures in C
提问by kristopolous
I would like this to work, but it does not:
我希望这个工作,但它不:
#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();
}
It actually doescompile and does work for 1 instance ... but the second one fails. Any idea how to get closures in C?
它实际上确实可以编译并且可以为 1 个实例工作……但是第二个实例失败了。知道如何在 C 中获得闭包吗?
It would be truly awesome!
真是太棒了!
采纳答案by ephemient
Using FFCALL,
使用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);
}
But usually in C you end up passing extra arguments around to fake closures.
但通常在 C 中,你最终会传递额外的参数来伪造闭包。
Apple has a non-standard extension to C called blocks, which do work much like closures.
Apple 有一个非标准的 C 扩展,称为blocks,它的工作方式与闭包非常相似。
回答by PADYMKO
The ANSI C has not a support for closure, as well as nested functions. Workaround for it is usage simple "struct".
ANSI C 不支持闭包以及嵌套函数。解决方法是使用简单的“结构”。
Simple example closure for sum two numbers.
对两个数字求和的简单示例闭包。
// 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;
}
Usage:
用法:
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));
}
Result:
结果:
10 + 1 = 11
10 + 3 = 13
10 + 2 = 12
10 + 4 = 14
10 + 5 = 15
On C++11 it will be achived by use lambda expression.
在 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);
}
A result, after compilation with a flag -std=c++11.
结果,编译后带有标志 -std=c++11。
10 + 1 = 11
10 + 2 = 12
10 + 3 = 13
10 + 4 = 14
10 + 5 = 15
回答by arsenm
GCC and clang have the blocks extension, which is essentially closures in C.
GCC 和 clang 具有块扩展,这在 C 中本质上是闭包。
回答by Richard Chambers
A Working Definition of a Closure with a JavaScript Example
使用 JavaScript 示例定义闭包
A closure is a kind of object that contains a pointer or reference of some kind to a function to be executed along with the an instance of the data needed by the function.
闭包是一种对象,它包含指向要执行的函数的某种指针或引用,以及函数所需的数据实例。
An example in JavaScript from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closuresis
来自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
};
}
which could then be used like:
然后可以像这样使用:
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
Some of the Obstacles to Overcome with C
用 C 克服的一些障碍
The C programming language is a statically typed language, unlike JavaScript, nor does it have garbage collection, and some other features that make it easy to do closures in JavaScript or other languages with intrinsic support for closures.
C 编程语言是一种静态类型语言,与 JavaScript 不同,它也没有垃圾收集和其他一些特性,这些特性可以很容易地在 JavaScript 或其他具有闭包内在支持的语言中进行闭包。
One large obstacle for closures in Standard C is the lack of language support for the kind of construct in the JavaScript example in which the closure includes not only the function but also a copy of data that is captured when the closure is created, a way of saving state which can then be used when the closure is executed along with any additional arguments provided at the time the closure function is invoked.
标准 C 中闭包的一个大障碍是缺乏对 JavaScript 示例中的构造类型的语言支持,其中闭包不仅包括函数,还包括创建闭包时捕获的数据副本,这是一种保存状态,然后可以在执行闭包时使用该状态以及调用闭包函数时提供的任何其他参数。
However C does have some basic building blocks which can provide the tools for creating a kind of closure. Some of the difficulties are (1) memory management is the duty of the programmer, no garbage collection, (2) functions and data are separated, no classes or class type mechanics, (3) statically typed so no run time discovery of data types or data sizes, and (4) poor language facilities for capturing state data at the time the closure is created.
然而,C 确实有一些基本的构建块,可以提供创建一种闭包的工具。一些困难是 (1) 内存管理是程序员的职责,没有垃圾收集,(2) 函数和数据是分开的,没有类或类类型机制,(3) 静态类型所以没有运行时发现数据类型或数据大小,以及 (4) 在创建闭包时捕获状态数据的语言设施很差。
One thing that makes something of a closure facility possible with C is the void *pointer and using unsigned charas a kind of general purpose memory type which is then transformed into other types through casting.
使 C 的闭包设施成为可能的一件事是void *指针并unsigned char用作一种通用内存类型,然后通过强制转换将其转换为其他类型。
An Implementation With Standard C and a Bit of Stretching Here and There
标准 C 的实现和一些扩展
NOTE:The following example depends on a stack based argument passing convention as is used with most x86 32 bit compilers. Most compilers also allow for a calling convention to be specified other than stack based argument passing such as the __fastcallmodifier of Visual Studio. The default for x64 and 64 bit Visual Studio is to use the __fastcallconvention by default so that function arguments are passed in registers and not on the stack. See Overview of x64 Calling Conventionsin the Microsoft MSDN as well as How to set function arguments in assembly during runtime in a 64bit application on Windows?as well as the various answers and comments in How are variable arguments implemented in gcc?.
注意:以下示例取决于大多数 x86 32 位编译器使用的基于堆栈的参数传递约定。大多数编译器还允许指定调用约定而不是基于堆栈的参数传递,例如__fastcallVisual Studio的修饰符。x64 和 64 位 Visual Studio__fastcall的默认设置是默认使用约定,以便函数参数在寄存器中而不是在堆栈中传递。请参阅Microsoft MSDN 中的 x64 调用约定概述以及如何在运行时在 Windows 上的 64 位应用程序中设置程序集中的函数参数?以及如何在 gcc 中实现变量参数中的各种答案和评论?.
One thing that we can do is to solve this problem of providing some kind of closure facility for C is to simplify the problem. Better to provide an 80% solution that is useful for a majority of applications than no solution at all.
我们可以做的一件事是解决为 C 提供某种闭包工具的问题,即简化问题。提供对大多数应用程序有用的 80% 解决方案比根本没有解决方案要好。
One such simplification is to only support functions that do not return a value, in other words functions declared as void func_name(). We are also going to give up compile time type checking of the function argument list since this approach builds the function argument list at run time. Neither one of these things that we are giving up are trivial so the question is whether the value of this approach to closures in C outweighs what we are giving up.
其中一种简化是仅支持不返回值的函数,即声明为 的函数void func_name()。我们还将放弃函数参数列表的编译时类型检查,因为这种方法在运行时构建函数参数列表。我们放弃的这些东西都不是微不足道的,所以问题是这种 C 中闭包方法的价值是否超过了我们放弃的价值。
First of all lets define our closure data area. The closure data area represents the memory area we are going to use to contain the information we need for a closure. The minimum amount of data I can think of is a pointer to the function to execute and a copy of the data to be provided to the function as arguments.
首先让我们定义我们的闭包数据区。闭包数据区表示我们将用于包含闭包所需信息的内存区域。我能想到的最小数据量是一个指向要执行的函数的指针和一个作为参数提供给函数的数据副本。
In this case we are going to provide any captured state data needed by the function as an argument to the function.
在这种情况下,我们将提供函数所需的任何捕获状态数据作为函数的参数。
We also want to have some basic safe guards in place so that we will fail reasonably safely. Unfortunately the safety rails are a bit weak with some of the work arounds we are using to implement a form of closures.
我们还希望有一些基本的安全防护措施,以便我们能够合理安全地失败。不幸的是,在我们用来实现某种形式的闭包的一些变通方法中,安全栏有点弱。
The Source Code
源代码
The following source code was developed using Visual Studio 2017 Community Edition in a .c C source file.
以下源代码是使用 Visual Studio 2017 社区版在 .c C 源文件中开发的。
The data area is a struct that contains some management data, a pointer to the function, and an open ended data area.
数据区是一个结构体,其中包含一些管理数据、一个指向函数的指针和一个开放式数据区。
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;
Next we create a function that will initialize a closure data area.
接下来我们创建一个函数来初始化一个闭包数据区。
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;
}
This function is designed to accept a pointer to a data area which gives flexibility as to how the user of the function wants to manage memory. They can either use some memory on the stack or static memory or they can use heap memory via the malloc()function.
这个函数被设计成接受一个指向数据区的指针,这为函数的用户想要如何管理内存提供了灵活性。它们可以使用堆栈上的一些内存或静态内存,也可以通过该malloc()函数使用堆内存。
unsigned char closure_area[512];
ClosureStruct *p = beginClosure (xFunc, 512, closure_area);
or
或者
ClosureStruct *p = beginClosure (xFunc, 512, malloc(512));
// do things with the closure
free (p); // free the malloced memory.
Next we provide a function that allows us to add data and arguments to our closure. The purpose of this function is to build up the closure data so that when closure function is invoked, the closure function will be provided any data it needs to do its job.
接下来我们提供一个函数,允许我们向闭包添加数据和参数。这个函数的目的是建立闭包数据,这样当闭包函数被调用时,闭包函数将获得完成其工作所需的任何数据。
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;
}
And to make this a bit simpler to use lets provide a wrapping macro which is generally handy but does have limitations since it is C Processor text manipulation.
并且为了使它更易于使用,让我们提供一个包装宏,它通常很方便,但确实有限制,因为它是 C 处理器文本操作。
#define PUSHDATA(cs,d) pushDataClosure((cs),sizeof(d),(d))
so we could then use something like the following source code:
所以我们可以使用如下源代码:
unsigned char closurearea[256];
int iValue = 34;
ClosureStruct *dd = PUSHDATA(beginClosure(z2func, 256, closurearea), iValue);
dd = PUSHDATA(dd, 68);
execClosure(dd);
Invoking the Closure: The execClosure() Function
调用闭包:execClosure() 函数
The last piece to this is the execClosure()function to execute the closure function with its data. What we are doing in this function is to copy the argument list supplied in the closure data structure onto the stack as we invoke the function.
最后一部分是execClosure()使用其数据执行闭包函数的函数。我们在这个函数中所做的是在调用函数时将闭包数据结构中提供的参数列表复制到堆栈中。
What we do is cast the args area of the closure data to a pointer to a struct containing an unsigned chararray and then dereference the pointer so that the C compiler will put a copy of the arguments onto the stack before it calls the function in the closure.
我们所做的是将闭包数据的 args 区域转换为指向包含unsigned char数组的结构的指针,然后取消引用该指针,以便 C 编译器在调用闭包中的函数之前将参数的副本放入堆栈。
To make it easier to create the execClosure()function, we will create a macro that makes it easy to create the various sizes of structs we need.
为了更容易地创建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);\
}
Then we use this macro to create a series of tests to determine how to call the closure function. The sizes chosen here may need tweaking for particular applications. These sizes are arbitrary and since the closure data will rarely be of the same size, this is not efficiently using stack space. And there is the possibility that there may be more closure data than we have allowed for.
然后我们使用这个宏来创建一系列测试来确定如何调用闭包函数。此处选择的尺寸可能需要针对特定应用进行调整。这些大小是任意的,并且由于闭包数据很少具有相同的大小,因此无法有效地使用堆栈空间。并且有可能有比我们允许的更多的关闭数据。
// 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;
}
We return the pointer to the closure in order to make it easily available.
我们将指针返回到闭包,以便使其易于使用。
An Example Using the Library Developed
使用开发的库的示例
We can use the above as follows. First a couple of example functions that don't really do much.
我们可以按如下方式使用上述内容。首先是几个没有真正做太多事情的示例函数。
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;
}
Next we build our closures and execute them.
接下来我们构建我们的闭包并执行它们。
{
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);
}
Which gives an output of
这给出了输出
i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
Well What About Currying?
那么咖喱呢?
Next we could make a modification to our closure struct to allow us to do currying of functions.
接下来,我们可以对闭包结构进行修改,以允许我们对函数进行柯里化。
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;
with the supporting functions for currying and resetting of a curry point being
支持柯里化和重置柯里点的功能
ClosureStruct *curryClosure(ClosureStruct *p)
{
p->nCurry = p->nBytes;
return p;
}
ClosureStruct *resetCurryClosure(ClosureStruct *p)
{
p->nBytes = p->nCurry;
return p;
}
The source code for testing this could be:
用于测试的源代码可能是:
{
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)));
}
with the output of
与输出
i = 45, 1234567890123
i = 68, 1234567890123
zFunc i = 45, j = 145, k = 185
zFunc i = 45, j = 145, k = 295
回答by outis
GCC supports inner functions, but not closures. C++0x will have closures. No version of C that I'm aware of, and certainly no standard version, provides that level of awesome.
GCC 支持内部函数,但不支持闭包。C++0x 将有闭包。我所知道的 C 版本,当然也没有标准版本,能提供这种级别的真棒。
Phoenix, which is part of Boost, provides closures in C++.
Phoenix是 Boost 的一部分,它在 C++ 中提供了闭包。
回答by user3761804
On this page you can find a description on how to do closures in C:
在此页面上,您可以找到有关如何在 C 中执行闭包的说明:
http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/
http://brodowsky.it-sky.net/2014/06/20/closures-in-c-and-scala/
The idea is that a struct is needed and that struct contains the function pointer, but gets provided to the function as first argument. Apart from the fact that it requires a lot of boiler plate code and the memory management is off course an issue, this works and provides the power and possibilities of other languages' closures.
这个想法是需要一个结构并且该结构包含函数指针,但作为第一个参数提供给函数。除了它需要大量样板代码和内存管理当然是一个问题之外,这有效并提供了其他语言闭包的能力和可能性。

