有没有办法在C里做cur?
-
06-07-2019 - |
题
假设我有一个指向函数 _stack_push(stack * stk,void * el)
的指针。我希望能够调用 curry(_stack_push,my_stack)
并获取一个只需要 void * el
的函数。我想不出办法,因为C不允许运行时函数定义,但我知道有比我更聪明的人:)。有什么想法吗?
解决方案
我找到了Laurent Dami的一篇论文,讨论了C / C ++ / Objective-C中的currying:
C / C ++ / Objective-c中具有咖喱功能的更多功能可重用性
对它在C中的实现方式感兴趣:
我们当前的实现使用现有的C构造来添加currying机制。这比修改编译器要容易得多,并且足以证明currying的兴趣。然而,这种方法有两个缺点。首先,curried函数不能进行类型检查,因此需要小心使用以避免错误。其次,curry函数无法知道其参数的大小,并将它们视为整数的大小。
其他提示
GCC为嵌套函数的定义提供了扩展。虽然这不是ISO标准C,但这可能会引起一些兴趣,因为它可以非常方便地回答这个问题。简而言之,嵌套函数可以访问父函数局部变量,父函数也可以返回它们的指针。
这是一个简短的,不言自明的例子:
#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)
可能会导致意外行为,因为 a
读取的变量 a
在<代码>部分已终止。
请参阅 GCC文档的这一部分。
这是我头脑中的第一次猜测(可能不是最佳解决方案)。
curry函数可以从堆中分配一些内存,并将参数值放入堆分配的内存中。然后诀窍是返回的函数知道它应该从堆分配的内存中读取它的参数。如果只返回函数的一个实例,那么指向这些参数的指针可以存储在单例/全局中。否则,如果返回的函数有多个实例,那么我认为curry需要在堆分配的内存中创建返回函数的每个实例(通过编写像“获取指针”这样的操作码参数“,”推送参数“,并”将该“其他函数调用”到堆分配的存储器中)。在这种情况下,你需要注意分配的内存是否可执行,也许(我不知道)甚至害怕反病毒程序。
这是一种在C中进行currying的方法。虽然这个示例应用程序使用C ++ iostream输出是为了方便,但它都是C样式编码。
这种方法的关键是要有一个 struct
,它包含一个 unsigned char
数组,这个数组用于构建一个函数的参数列表。要调用的函数被指定为推入数组的参数之一。然后将结果数组提供给代理函数,该代理函数实际执行函数和参数的闭包。
在这个例子中,我提供了几个特定于类型的辅助函数来将参数推送到闭包中,以及一个通用的 pushMem()
函数来推送 struct
或其他记忆区域。
这种方法确实需要分配一个存储区,然后用于闭包数据。最好将堆栈用于此内存区域,以便内存管理不会成为问题。还有一个问题是关闭存储区域的大小,以便有足够的空间容纳必要的参数,但不要太大,以至于内存或堆栈中的多余空间被未使用的空间占用。
我已经尝试使用稍微不同定义的闭包结构,该结构包含用于存储闭包数据的当前使用的数组大小的附加字段。然后,这个不同的闭包结构与修改的辅助函数一起使用,这样就不需要辅助函数的用户在向闭包结构添加参数时维护自己的 unsigned char *
指针。
备注和警告
使用Visual Studio 2013编译和测试以下示例程序。此示例的输出如下所示。我不确定GCC或CLANG在这个例子中的用途,也不确定64位编译器可能会出现的问题,因为我认为我的测试是使用32位应用程序。此方法似乎只适用于使用标准C声明的函数,其中调用函数在被调用者返回后处理从堆栈中弹出参数( __ cdecl
而不是 __ stdcall 代码>在Windows API中。)
由于我们在运行时构建参数列表然后调用代理函数,因此这种方法不允许编译器对参数执行检查。由于编译器无法标记的参数类型不匹配,这可能导致神秘的故障。
示例应用
// 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((*)(...)))m.p)(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 " << m.i << " " << m.xc << ";";
return m.i + 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;
}
测试输出
此示例程序的输出。括号中的数字是主函数调用的主编号。
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)