假设我有一个指向函数 _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函数无法知道其参数的大小,并将它们视为整数的大小。

本文不包含 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)
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top