我怎样才能使一个函数返回一个函数?

大图:我有一个function模块和一个模块,具有这些function的程序和function。

当我结合两个函数(从函数的模块接口):

double f1(double alpha, double x); double f2(double beta, double x); 

在几个方面,(其中之一是添加):

 double OP_Addition(double (*f)(double,double) , double (*g)(double,double), double param1, double param2, double x); 

以下(一块)实现没有问题:

 z1 = (*f)(param1, x); z2 = (*g)(param2, x); y = z1 + z2; return y; 

但是当我想返回一个指向“新”函数的指针,如:

 void *OP_PAdd( double (*f)(double,double), double param3 ); 

我无法让它正常工作,也没有做出正确的“呼叫”。 我想在其他function中使用输出“function”作为input。

其他的答案是正确和有趣的,但是你应该知道,在便携式C99中, 没有办法将真正的封闭作为C函数 (这是C的一个基本限制)。 如果您不知道什么是封闭的,请仔细阅读wiki页面(也读SICP ,特别是其中的§1.3 )。 不过要注意的是,在C ++ 11中,你有使用std :: function和lambdaexpression式的闭包。 而其他大多数编程语言(Ocaml,Haskell,Javascript,Lisp,Clojure,Python,…)都已经closures了。

由于在C中缺乏真正的闭包(在math上,C函数中唯一的闭包值是全局或静态variables或文本),大多数接受C函数指针的库提供了一些API处理callback函数和一些客户端数据(一个简单的例子可能是qsort_r ,但更严重的看GTK里面)。 客户端数据(通常是不透明的指针)可以用来保持closures的值。 你可能想遵循一个类似的约定(所以系统地传递函数指针作为具有一些额外的客户端数据的callback函数),所以你需要改变C函数的签名(而不是只传递一个原始的函数指针,你会通过一个函数指针和一些客户端数据作为callback,来“模拟”闭包)。

有时可能会在运行时生成一个C函数(使用非标准的function,可能在操作系统或某个外部库的帮助下)。 你可以使用一些JIT编译库,如GNU闪电 , libjit (两者都会快速生成一些运行缓慢的代码), asmjit (你将会明确地生成每个机器指令,而且你有责任发出快速的x86-64代码), GCCJIT或LLVM (都在现有的编译器之上,所以可以用来缓慢地发出一些优化的代码)。 在POSIX和Linux系统上,你也可以在一些临时文件/tmp/tempcode.c发出一些C代码,派生一个编译文件(例如gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so )插入到插件中,并使用dlopen(3) & dlsym(3)dynamic加载生成的插件。

顺便说一句,我们不知道你正在编写的实际应用程序是什么,但你可能会考虑在其中embedded一些解​​释器,例如Lua或Guile 。 然后,您将使用embedded的评估者/解释器并提供callback。

当从另一个函数返回一个函数时,最简单的方法是使用typedef

 typedef double (*ftype)(double, double); 

然后你可以像这样声明你的函数:

 ftype OP_PAdd( ftype f, double param3 ) { .... return f1; } 

你可以在没有typedef情况下做到这一点,但是很麻烦:

 double (*OP_PAdd( double (*f)(double,double), double param3 ))(double,double) { return f1; } 

因此,当函数指针作为参数或其他函数的返回值时,请使用typedef

编辑:

虽然你可以声明这样的types:

 typedef double ftype(double, double); 

在实践中,你永远不能直接使用这种types。 一个函数不能返回一个函数(只有一个指向函数的指针),并且这个types的variables不能被赋值。

另外,你不需要显式地引用一个函数指针来调用函数,所以指针本身被隐藏的事实不是一个大问题。 将函数指针定义为typedef也是惯例。 从signal手册页 :

  #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 

你的意思是这样吗? decider()函数返回一个指向另一个函数的指针,然后调用它。

 #include <stdio.h> #include <stdlib.h> typedef double(*fun)(double, double); double add(double a, double b) { return a + b; } double sub(double a, double b) { return a - b; } double mul(double a, double b) { return a * b; } fun decider(char op) { switch(op) { case '+': return add; case '-': return sub; case '*': return mul; } exit(1); } int main(void) { fun foo; foo = decider('+'); printf("%f\n", foo(42.0, 24.0)); foo = decider('-'); printf("%f\n", foo(42.0, 24.0)); foo = decider('*'); printf("%f\n", foo(42.0, 24.0)); return 0; } 

节目输出:

 66.000000 18.000000 1008.000000 

编辑:根据@dbush答案下的评论,这个版本作为一个指针退回到typedef ,只是一个函数。 它给出了相同的输出,但在decider()它干净地编译并给出正确的输出,不pipe我是否写return add;return &add;

 #include <stdio.h> #include <stdlib.h> typedef double(fun)(double, double); double add(double a, double b) { return a + b; } double sub(double a, double b) { return a - b; } double mul(double a, double b) { return a * b; } fun *decider(char op) { switch(op) { case '+': return add; // return &add; case '-': return sub; case '*': return mul; } exit(1); } int main(void) { fun *foo; foo = decider('+'); printf("%f\n", foo(42.0, 24.0)); foo = decider('-'); printf("%f\n", foo(42.0, 24.0)); foo = decider('*'); printf("%f\n", foo(42.0, 24.0)); return 0; } 

在C中,你可以返回指向函数的指针,但是要做到这一点,函数需要先存在,dynamic创build函数不是C所说的可能的事情,不必介意怎么做

如果您的代码只能在一个操作系统和一个处理器上工作(可能还有其他一些限制),您可能能够:

  1. 分配内存页面
  2. 写数据和机器代码做你想做的,调用通过指针传递的函数等
  3. 将存储器保护从读取/写入更改为读取/执行
  4. 返回指向创build函数的指针
  5. 不要担心,你需要每个function4kB

那里可能有某个地方的图书馆,但是一定不能携带

所以你需要一个函数来返回一个函数的指针。

 double retfunc() { return 0.5; } double (*fucnt)() { return retfunc; } main() { printf("%f\n", (*funct())()); } 

由于有些人显然是在写一个黑客来解决这个问题,所以这是一个不太好的方法来做到这一点:使用setjmp和longjmp的静态结构。

 jmp_buf jb; void *myfunc(void) { static struct { // put all of your local variables here. void *new_data, *data; int i; } *_; _ = malloc(sizeof(*_)); _.data = _; if (!(_.new_data = (void *)(intptr_t)setjmp(jb))) return _.data; _.data = _.new_data; /* put your code here */ free(_); return NULL; } 

为了解释这里发生了什么,setjmp将在创build跳转缓冲区时返回值0,否则返回longjmp传递的值(例如,longjmp(jb,5)将导致setjmp返回5)。

所以,我们正在做的是让我们的函数返回一个指向它分配的数据结构的指针; 然后调用我们的封闭像:

 void *data = myfunc(); longjmp(jb, (int)(intptr_t)data); 

请注意,int不能保证足够大以在所有平台上存储指针; 所以你可能需要创build一个数据池,并通过句柄(池中的索引)返回/传递数据。

正如我之前所说的,闭包只是一个函数,它的所有数据都是在堆中分配的。

我一直在为N64和PSP游戏写黑​​客多年。 人们声称这是不可能的,可能从来没有被这种东西玷污。 主要归结为缺乏经验。

我会在这里变得非常黑,所以请坚持你的马裤。

标准的C api带有一个叫做setjmplongjmp函数。 不好的命名,他们实质上做的是将当前状态(包括堆栈位置和寄存器值)的副本存储到jmp_buf (或技术名称, continuation )中。

现在,可以说你创build一个函数:

 jmp_buf jb; void sfunc(void) { void *sp_minus1 = 0xBEEFBABE; setjmp(jb); } 

当你调用sfunc时,会创build一个栈帧。 因为这个函数没有参数,栈中的第一个入口就是返回地址,紧接着它是sp_minus1对象。

为什么这是相关的? 那么,sp_minus1的地址是相对于堆栈帧的开始。 如果你可以在jbfind栈帧的地址,你可以改变它…说,堆到一个位置?

我们现在所掌握的是一种为堆上的longjmp函数调用创build堆栈框架的方法,该堆栈可以包含有关它们被调用的上下文的附加状态; 或换句话说,closures。

我不认为我曾经见过这样的人使用longjmp / setjmp,但是如果你正在寻找一种dynamic生成和返回C函数的方法,我认为这将是你最好的路线。

编辑:

这里是我描述的黑客的一个示例实现:

 #include <inttypes.h> // intptr_t #include <setjmp.h> // longjmp, setjmp #include <stdio.h> // printf #include <stdlib.h> // malloc, free #include <string.h> // memcpy typedef struct { jmp_buf jb; int fixupc; int fixupv[10]; size_t stack_size; // this is only an approximation void *stack_ptr; } CLOSURE; int getclosure(CLOSURE *closure) { unsigned int i, size; void *i_ptr = &i, *sp; unsigned char *data = (unsigned char *)(void *)closure->jb; memset(closure, 0, sizeof(CLOSURE)); if (!setjmp(closure->jb)) { printf("looking for 0x%08X...\n\n", (unsigned int)(intptr_t)i_ptr); for (i = 0; i < sizeof(closure->jb); i++) { memcpy(&sp, &data[i], sizeof(void *)); size = (unsigned int)(intptr_t)(sp - i_ptr); if (size < 0x300) { closure->fixupv[closure->fixupc++] = i; printf(" fixup @ 0x%08X\n", (unsigned int)(intptr_t)sp); if (sp > closure->stack_ptr) { closure->stack_size = size; closure->stack_ptr = sp; } } } if (!closure->stack_ptr) return 0; printf("\nsp @ 0x%08X\n", (unsigned int)(intptr_t)closure->stack_ptr); printf("# of fixups = %i\n", closure->fixupc); /* * once we allocate the new stack on the heap, we'll need to fixup * any additional stack references and memcpy the current stack. * * for the sake of this example, I'm only fixing up the pointer * to the stack itself. * * after that, we would have successfully created a closure... */ closure->stack_size = 1024; sp = malloc(closure->stack_size); memcpy(sp, closure->stack_ptr, closure->stack_size); memcpy(&data[closure->fixupv[0]], &sp, sizeof(void *)); closure->stack_ptr = sp; return 1; } else { /* * to this bit of code right here */ printf("holy shit!\n"); return 0; }; } void newfunc(CLOSURE *closure) { longjmp(closure->jb, 1); } void superfunc(CLOSURE *closure) { newfunc(closure); } int main(int argc, char *argv[]) { CLOSURE c; if (getclosure(&c)) { printf("\nsuccess!\n"); superfunc(&c); free(c.stack_ptr); return 0; } return 0; } 

这在技术上是一种堆栈压缩的forms,所以默认情况下,GCC将生成导致程序中止的堆栈。 如果你使用'-fno-stack-protection'进行编译,它将会工作。