有一种方法可以在C中进行curl吗?

假设我有一个指向函数_stack_push(stack* stk, void* el)的指针。 我希望能够调用curry(_stack_push, my_stack)并取回一个只需要void* el的函数。 我想不出有办法做到这一点,因为C不允许运行时函数的定义,但我知道在这里有比我更聪明的人:)。 有任何想法吗?

我find了Laurent Dami的一篇论述C / C ++ / Objective-C中的currying的论文:

C / C ++ / Objective-C中具有Curriedfunction的更多function可重用性

感兴趣的是如何在C:

我们目前的实现使用现有的C构造来添加currying机制。 这比修改编译器容易得多,足以certificatecurrying的兴趣。 但是这种方法有两个缺点。 首先,curried函数不能进行types检查,因此需要小心使用以避免错误。 其次,咖喱函数无法知道它的参数的大小,并把它们看作是一个整数的大小。

本文不包含curry()的实现,但您可以想象如何使用函数指针和可变参数函数来实现它。

GCC为嵌套函数的定义提供了一个扩展。 虽然这不是ISO标准C,但这可能会引起一些兴趣,因为它可以很方便地回答问题。 简而言之,嵌套函数可以访问父函数的局部variables,而指向它们的指针也可以由父函数返回。

这是一个简短的,不言自明的例子:

 #include <stdio.h> typedef int (*two_var_func) (int, int); typedef int (*one_var_func) (int); int add_int (int a, int b) { return a+b; } one_var_func partial (two_var_func f, int a) { int g (int b) { return f (a, b); } return g; } int main (void) { int a = 1; int b = 2; printf ("%d\n", add_int (a, b)); printf ("%d\n", partial (add_int, a) (b)); } 

然而,这种结构有一个限制。 如果你保留一个指向结果函数的指针,如

 one_var_func u = partial (add_int, a); 

函数调用u(0)可能会导致意外的行为,因为读取的variablesapartial终止之后被破坏。

请参阅GCC文档的这一部分 。

这是我的头一个猜测(可能不是最好的解决scheme)。

curry函数可以从堆中分配一些内存,并将参数值放入堆分配的内存中。 然后诀窍是返回的函数知道它应该从堆分配的内存中读取参数。 如果只有一个返回函数的实例,那么指向这些参数的指针可以存储在单例/全局中。 否则,如果返回的函数有多个实例,那么我认为curry需要在堆分配的内存中创build返回函数的每个实例(通过编写操作码,如“获取指向参数的指针”,“推送参数“和”调用其他函数“到堆分配的内存中)。 在这种情况下,你需要小心是否分配的内存是可执行的,也许(我不知道)甚至害怕防病毒程序。

下面是一个做C的方法。虽然这个示例应用程序为了方便使用C ++ iostream输出,但它都是C风格的编码。

这种方法的关键是有一个struct ,其中包含一个unsigned char数组,这个数组是用来build立一个函数的参数列表。 要被调用的函数被指定为被推入到数组中的参数之一。 然后将得到的数组赋给一个实际执行函数和参数闭包的代理函数。

在这个例子中,我提供了一些types特定的帮助函数来将参数推入闭包,以及一个通用pushMem()函数来推送struct或其他内存区域。

这种方法确实需要分配一个存储区域,然后用于closures数据。 这个内存区域最好使用堆栈,这样内存pipe理就不会成为问题。 还有一个问题,即closures存储内存区有多大,以便有足够的空间来存储必要的参数,但不会太大,以至于内存或堆栈中的多余空间被未使用的空间占用。

我已经尝试过使用稍微不同的定义的闭包结构,它包含用于存储闭包数据的当前使用的数组大小的附加字段。 然后将这个不同的闭包结构与一个修改过的帮助函数一起使用,这样在向闭包结构中添加参数时就不需要帮助函数的用户维护自己的unsigned char *指针了。

注意事项和注意事项

以下示例程序是使用Visual Studio 2013编译和testing的。此示例的输出如下所示。 我不确定在这个例子中使用GCC或CLANG,我也不确定使用64位编译器可能会出现的问题,因为我的印象是我的testing是使用32位应用程序。 也可以这样做,似乎只能使用标准C声明的函数,调用函数在调用函数返回后从栈中popup参数(Windows API中的__cdecl而不是__stdcall )。

由于我们在运行时构build参数列表,然后调用代理函数,因此此方法不允许编译器对参数执行检查。 这可能会导致由于编译器无法标记的参数types不匹配而导致的神秘故障。

示例应用程序

 // currytest.cpp : Defines the entry point for the console application. // // while this is C++ usng the standard C++ I/O it is written in // a C style so as to demonstrate use of currying with C. // // this example shows implementing a closure with C function pointers // along with arguments of various kinds. the closure is then used // to provide a saved state which is used with other functions. #include "stdafx.h" #include <iostream> // notation is used in the following defines // - tname is used to represent type name for a type // - cname is used to represent the closure type name that was defined // - fname is used to represent the function name #define CLOSURE_MEM(tname,size) \ typedef struct { \ union { \ void *p; \ unsigned char args[size + sizeof(void *)]; \ }; \ } tname; #define CLOSURE_ARGS(x,cname) *(cname *)(((x).args) + sizeof(void *)) #define CLOSURE_FTYPE(tname,m) ((tname((*)(...)))(m).p) // define a call function that calls specified function, fname, // that returns a value of type tname using the specified closure // type of cname. #define CLOSURE_FUNC(fname, tname, cname) \ tname fname (cname m) \ { \ return ((tname((*)(...)))mp)(CLOSURE_ARGS(m,cname)); \ } // helper functions that are used to build the closure. unsigned char * pushPtr(unsigned char *pDest, void *ptr) { *(void * *)pDest = ptr; return pDest + sizeof(void *); } unsigned char * pushInt(unsigned char *pDest, int i) { *(int *)pDest = i; return pDest + sizeof(int); } unsigned char * pushFloat(unsigned char *pDest, float f) { *(float *)pDest = f; return pDest + sizeof(float); } unsigned char * pushMem(unsigned char *pDest, void *p, size_t nBytes) { memcpy(pDest, p, nBytes); return pDest + nBytes; } // test functions that show they are called and have arguments. int func1(int i, int j) { std::cout << " func1 " << i << " " << j; return i + 2; } int func2(int i) { std::cout << " func2 " << i; return i + 3; } float func3(float f) { std::cout << " func3 " << f; return f + 2.0; } float func4(float f) { std::cout << " func4 " << f; return f + 3.0; } typedef struct { int i; char *xc; } XStruct; int func21(XStruct m) { std::cout << " fun21 " << mi << " " << m.xc << ";"; return mi + 10; } int func22(XStruct *m) { std::cout << " fun22 " << m->i << " " << m->xc << ";"; return m->i + 10; } void func33(int i, int j) { std::cout << " func33 " << i << " " << j; } // define my closure memory type along with the function(s) using it. CLOSURE_MEM(XClosure2, 256) // closure memory CLOSURE_FUNC(doit, int, XClosure2) // closure execution for return int CLOSURE_FUNC(doitf, float, XClosure2) // closure execution for return float CLOSURE_FUNC(doitv, void, XClosure2) // closure execution for void // a function that accepts a closure, adds additional arguments and // then calls the function that is saved as part of the closure. int doitargs(XClosure2 *m, unsigned char *x, int a1, int a2) { x = pushInt(x, a1); x = pushInt(x, a2); return CLOSURE_FTYPE(int, *m)(CLOSURE_ARGS(*m, XClosure2)); } int _tmain(int argc, _TCHAR* argv[]) { int k = func2(func1(3, 23)); std::cout << " main (" << __LINE__ << ") " << k << std::endl; XClosure2 myClosure; unsigned char *x; x = myClosure.args; x = pushPtr(x, func1); x = pushInt(x, 4); x = pushInt(x, 20); k = func2(doit(myClosure)); std::cout << " main (" << __LINE__ << ") " << k << std::endl; x = myClosure.args; x = pushPtr(x, func1); x = pushInt(x, 4); pushInt(x, 24); // call with second arg 24 k = func2(doit(myClosure)); // first call with closure std::cout << " main (" << __LINE__ << ") " << k << std::endl; pushInt(x, 14); // call with second arg now 14 not 24 k = func2(doit(myClosure)); // second call with closure, different value std::cout << " main (" << __LINE__ << ") " << k << std::endl; k = func2(doitargs(&myClosure, x, 16, 0)); // second call with closure, different value std::cout << " main (" << __LINE__ << ") " << k << std::endl; // further explorations of other argument types XStruct xs; xs.i = 8; xs.xc = "take 1"; x = myClosure.args; x = pushPtr(x, func21); x = pushMem(x, &xs, sizeof(xs)); k = func2(doit(myClosure)); std::cout << " main (" << __LINE__ << ") " << k << std::endl; xs.i = 11; xs.xc = "take 2"; x = myClosure.args; x = pushPtr(x, func22); x = pushPtr(x, &xs); k = func2(doit(myClosure)); std::cout << " main (" << __LINE__ << ") " << k << std::endl; x = myClosure.args; x = pushPtr(x, func3); x = pushFloat(x, 4.0); float dof = func4(doitf(myClosure)); std::cout << " main (" << __LINE__ << ") " << dof << std::endl; x = myClosure.args; x = pushPtr(x, func33); x = pushInt(x, 6); x = pushInt(x, 26); doitv(myClosure); std::cout << " main (" << __LINE__ << ") " << std::endl; return 0; } 

testing输出

这个示例程序的输出。 括号中的数字是进行函数调用的主要行号。

  func1 3 23 func2 5 main (118) 8 func1 4 20 func2 6 main (128) 9 func1 4 24 func2 6 main (135) 9 func1 4 14 func2 6 main (138) 9 func1 4 16 func2 6 main (141) 9 fun21 8 take 1; func2 18 main (153) 21 fun22 11 take 2; func2 21 main (161) 24 func3 4 func4 6 main (168) 9 func33 6 26 main (175)