大图:我有一个带有功能的模块和一个带有这些功能的程序和功能的模块。
当我组合两个函数时(来自函数的模块接口):
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 );
但是当我想要返回一个指向“new”函数的指针时,例如:
-outputFormatOptions includePunctuationDependencies
我无法让它正常工作,也没有做出正确的“通话”。我想在其他函数中使用输出“function”作为输入。
答案 0 :(得分:32)
从其他函数返回函数时,最简单的方法是使用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
。
编辑:
虽然你可以声明这样的类型:
typedef double ftype(double, double);
在实践中,你永远不能直接使用这样的类型。函数不能返回函数(仅指向函数的指针),并且不能将此类型的变量赋值给。
此外,您不需要显式取消引用函数指针来调用该函数,因此指针本身被隐藏的事实不是一个大问题。将函数指针定义为typedef
也是惯例。来自man page for signal
:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
答案 1 :(得分:25)
其他答案是正确和有趣的,但你应该知道,在便携式C99中,无法将真正的closures作为C函数(这是 C)的基本限制。如果你不知道什么是闭包,请仔细阅读the维基页面(并阅读SICP,特别是§1.3)。但请注意,在C++11中,您使用std::function和lambda-expressions进行了关闭。大多数其他编程语言(Ocaml,Haskell,Javascript,Lisp,Clojure,Python,......)都有闭包。
由于C中缺少真正的闭包(“数学上”C函数中唯一的闭合值是全局变量或静态变量或文字),大多数接受C函数指针的库都为某些客户端提供API处理callbacks数据(一个简单的例子可能是qsort_r,但更严肃地看一下GTK)。客户端数据(通常是不透明指针)可用于保持闭合值。您可能希望遵循类似的约定(因此系统地将函数指针作为带有一些额外客户端数据的回调传递),因此您需要更改C函数的签名(而不是只传递原始函数指针,您将通过函数指针和一些客户端数据作为回调,以“模拟”闭包。)
有时您可以在运行时生成C函数(使用非标准功能,可能借助操作系统或某些外部库)。您可能会使用JIT compiling,GNU lightning这样的libjit库(两者都会快速生成一些运行缓慢的代码),asmjit(您将明确生成每台机器指令,并且你有责任发出快速的x86-64代码,GCCJIT或LLVM(两者都在现有的编译器之上,因此可以用来缓慢地发出一些优化的代码)。在POSIX&amp; Linux系统,你也可以在一些临时文件/tmp/tempcode.c
中发出一些C代码,将该代码的编译(例如gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so
)分成一个插件,并使用dlopen(3)动态加载生成的插件&安培; dlsym(3) ..
顺便说一下,我们不知道您编写的实际应用程序是什么,但您可能会考虑在其中嵌入一些解释器,例如: Lua或Guile。然后,您将使用并向嵌入式评估程序/解释程序提供回调。
答案 2 :(得分:13)
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()
中,无论我是写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;
}
答案 3 :(得分:3)
在C中,你可以返回指向函数的指针,但要做到这一点,函数需要先存在,动态创建函数不是C所说的可能,更不用说怎么做了
如果您的代码仅在一个操作系统和一个处理器上运行(可能还有一些其他限制),您可以:
可能存在某些库,但必然不可移植
答案 4 :(得分:0)
因此,您希望函数返回指向函数的指针。
double retfunc()
{
return 0.5;
}
double (*fucnt)()
{
return retfunc;
}
main()
{
printf("%f\n", (*funct())());
}
答案 5 :(得分:0)
由于有些人显然对写一个黑客来解决这个问题感到妄想,所以这里采用一种不太常见的方法:使用带有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将在创建跳转缓冲区时返回值0,否则它将返回longjmp传递的值(例如,longjmp(jb,5)将导致setjmp返回5)。
所以,我们正在做的是让我们的函数返回指向它的分配数据结构的指针;然后调用我们的闭包像:
void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);
请注意,int不能保证足够大以在所有平台上存储指针;所以你可能需要创建一个数据池并按句柄(池中的索引)返回/传递数据。
正如我之前所说,闭包只是一个函数,它在堆上分配了所有数据。
我多年来一直在为N64和PSP游戏编写黑客。声称这是不可能的人可能从来没有用这种东西修补过。大多数情况下归结为缺乏经验。
答案 6 :(得分:-1)
我会在这里变得非常hacky,所以坚持你的马裤。
标准C api带有一个名为setjmp
和longjmp
的2个函数。除了命名不好之外,他们基本上做的是将当前状态的副本(包括堆栈位置和寄存器值)存储到jmp_buf
(或技术名称,continuation
)。
现在,假设您创建了一个函数:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
调用sfunc时,将创建堆栈帧。因为此函数没有参数,所以堆栈中的第一个条目将是返回地址,紧接着它将是sp_minus1对象。
为什么这有关系?好吧,sp_minus1的地址是相对于堆栈帧的开始。如果您可以在jb
中找到堆栈帧的地址,您可以将其更改为...比如堆中的某个位置?
我们在这一点上得到的是一种为堆上的longjmp函数调用创建堆栈帧的方法,它可以包含有关它们被调用的上下文的附加状态;或者换句话说,关闭。
我认为我从未见过有人以这种方式使用longjmp / setjmp,但如果您正在寻找一种在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;
}
这在技术上是一种堆栈粉碎方式,因此默认情况下,GCC会生成堆栈金丝雀,导致程序中止。如果使用&#39; -fno-stack-protection&#39;进行编译,它就可以正常工作。