我怎样才能使 returns 成为一个函数?
How can I make a function that returns a 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;
但是当我想 return 一个指向 "new" 函数的指针时,类似于:
void *OP_PAdd( double (*f)(double,double), double param3 );
我无法让它正常工作,也没有做出正确的 "call"。我想使用输出 "function" 作为其他函数的输入。
当从另一个函数 return 调用一个函数时,最简洁的方法是使用 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;
}
因此,当您将函数指针作为参数或其他函数的 return 值时,请使用 typedef
。
编辑:
虽然您可以这样声明类型:
typedef double ftype(double, double);
你永远不能在实践中直接使用这样的类型。一个函数不能return一个函数(只能是一个指向函数的指针),这个类型的变量不能赋值。
此外,您不需要显式取消引用函数指针来调用该函数,因此指针本身被隐藏这一事实并不是什么大问题。将函数指针定义为 typedef
也是惯例。来自 man page for signal
:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
你的意思是这样的吗? decider()
函数 returns 指向另一个函数的指针,然后调用该函数。
#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;
}
在 C 中,你可以 return 指向函数的指针,但要做到这一点,函数首先需要存在,动态创建函数不是 C 所说的是可能的,不管如何去做
如果您的代码仅适用于一个 OS 和一个处理器(可能还有一些其他限制),您可以:
- 分配内存页
- 随心所欲地编写数据和机器代码,调用指针传递的函数等
- 将内存保护从 read/write 更改为 read/execute
- return 指向已创建函数的指针
- 不用担心每个函数需要 4kB
可能有某个地方的库,但不一定是可移植的
其他答案正确且有趣,但您应该知道,在可移植的 C99 中,无法获得正版 closures as C functions (and this is a fundamental limitation of C). If you are not aware of what closures are, read the wiki page carefully on them (and also read SICP, notably its §1.3). Notice however that in C++11 you do have closures, using std::function and lambda-expressions。大多数其他编程语言(Ocaml、Haskell、Javascript、Lisp、Clojure、Python、....)都有闭包。
由于 C 中缺少真正的闭包("mathematically" C 函数中唯一的闭包值是全局或静态变量或文字),大多数接受 C 函数指针的库都提供 API 处理callbacks with some client data (a simple example could be qsort_r, but more seriously look inside GTK)。客户端数据(通常是不透明的指针)可用于保存封闭值。您可能希望遵循类似的约定(因此系统地将函数指针作为回调传递给一些额外的客户端数据),因此您需要更改 C 函数的签名(而不是仅传递原始函数指针,您将传递函数指针和一些客户端数据作为回调,到 "emulate" 闭包)。
您有时可以在运行时生成 C 函数(使用非标准功能,可能借助操作系统或某些外部库)。您可能会使用一些 JIT compiling library such as GNU lightning, libjit (both would generate some slow-running code quickly), asmjit (you'll generate each machine instruction explicitly, and it is your responsibility to emit fast x86-64 code), GCCJIT or LLVM (both are above existing compilers so can be used to emit -a bit slowly- some optimized code). On POSIX & Linux systems, you could also emit some C code in some temporary file /tmp/tempcode.c
, fork a compilation (e.g. gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so
) of that code into a plugin, and dynamically load that generated plugin using dlopen(3) & dlsym(3)..
顺便说一句,我们不知道您正在编写的实际应用程序是什么,但您可以考虑在其中嵌入一些解释器,例如Lua or Guile。然后,您将使用并提供对嵌入式 evaluator/interpreter.
的回调
我要在这里变得很老套,所以抓紧你的马裤。
标准 C api 带有两个函数,分别是 setjmp
和 longjmp
。除了糟糕的命名,他们实际上所做的是将当前状态(包括堆栈位置和寄存器值)的副本存储到 jmp_buf
(或者,技术名称,continuation
)。
现在,假设您创建了一个函数:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
当您调用 sfunc 时,将创建一个堆栈帧。因为此函数没有参数,堆栈中的第一个条目将是 return 地址,紧随其后的是 sp_minus1 对象。
为什么这很重要?那么,sp_minus1 的地址是相对于栈帧的开始的。如果你能在 jb
中找到堆栈帧的地址,你可以更改它...比如说,到堆中的某个位置?
此时我们得到的是一种为堆上的 longjmp 函数调用创建堆栈帧的方法,它可以包含有关调用它们的上下文的附加状态;或者换句话说,闭包。
我认为我从未见过任何人以这种方式使用 longjmp/setjmp,但是如果您正在寻找一种在 C 中动态生成和 return 函数的方法,我认为这将是您的最佳路线。
编辑:
下面是我描述的黑客攻击的实施示例:
#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 会生成导致程序中止的堆栈金丝雀。如果您使用“-fno-stack-protection”进行编译,它会起作用。
所以你想要一个函数 return 指向函数的指针。
double retfunc()
{
return 0.5;
}
double (*fucnt)()
{
return retfunc;
}
main()
{
printf("%f\n", (*funct())());
}
由于有些人显然偏执于编写 hack 来解决这个问题,这里有一个不太 hacky 的方法:使用带有 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 将 return 值为 0,否则它将 return longjmp 传递的值(例如 longjmp(jb , 5) 将导致 setjmp 为 return 5).
所以,我们正在做的是让我们的函数 return 指向它分配的数据结构的指针;然后像这样调用我们的闭包:
void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);
请注意,不能保证 int 的大小足以在所有平台上存储指针;因此您可能需要创建一个数据池,并 return/pass 按句柄(池中的索引)处理数据。
正如我之前所说,闭包只是一个函数,它的所有数据都分配在堆上。
多年来,我一直在为 N64 和 PSP 游戏编写 hack。声称这是不可能的人可能从来没有修补过这类东西。主要归结为缺乏经验。
大图:我有一个包含函数的模块和一个包含这些函数的过程和函数的模块。
当我组合两个函数时(来自函数的模块接口):
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;
但是当我想 return 一个指向 "new" 函数的指针时,类似于:
void *OP_PAdd( double (*f)(double,double), double param3 );
我无法让它正常工作,也没有做出正确的 "call"。我想使用输出 "function" 作为其他函数的输入。
当从另一个函数 return 调用一个函数时,最简洁的方法是使用 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;
}
因此,当您将函数指针作为参数或其他函数的 return 值时,请使用 typedef
。
编辑:
虽然您可以这样声明类型:
typedef double ftype(double, double);
你永远不能在实践中直接使用这样的类型。一个函数不能return一个函数(只能是一个指向函数的指针),这个类型的变量不能赋值。
此外,您不需要显式取消引用函数指针来调用该函数,因此指针本身被隐藏这一事实并不是什么大问题。将函数指针定义为 typedef
也是惯例。来自 man page for signal
:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
你的意思是这样的吗? decider()
函数 returns 指向另一个函数的指针,然后调用该函数。
#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;
}
在 C 中,你可以 return 指向函数的指针,但要做到这一点,函数首先需要存在,动态创建函数不是 C 所说的是可能的,不管如何去做
如果您的代码仅适用于一个 OS 和一个处理器(可能还有一些其他限制),您可以:
- 分配内存页
- 随心所欲地编写数据和机器代码,调用指针传递的函数等
- 将内存保护从 read/write 更改为 read/execute
- return 指向已创建函数的指针
- 不用担心每个函数需要 4kB
可能有某个地方的库,但不一定是可移植的
其他答案正确且有趣,但您应该知道,在可移植的 C99 中,无法获得正版 closures as C functions (and this is a fundamental limitation of C). If you are not aware of what closures are, read the wiki page carefully on them (and also read SICP, notably its §1.3). Notice however that in C++11 you do have closures, using std::function and lambda-expressions。大多数其他编程语言(Ocaml、Haskell、Javascript、Lisp、Clojure、Python、....)都有闭包。
由于 C 中缺少真正的闭包("mathematically" C 函数中唯一的闭包值是全局或静态变量或文字),大多数接受 C 函数指针的库都提供 API 处理callbacks with some client data (a simple example could be qsort_r, but more seriously look inside GTK)。客户端数据(通常是不透明的指针)可用于保存封闭值。您可能希望遵循类似的约定(因此系统地将函数指针作为回调传递给一些额外的客户端数据),因此您需要更改 C 函数的签名(而不是仅传递原始函数指针,您将传递函数指针和一些客户端数据作为回调,到 "emulate" 闭包)。
您有时可以在运行时生成 C 函数(使用非标准功能,可能借助操作系统或某些外部库)。您可能会使用一些 JIT compiling library such as GNU lightning, libjit (both would generate some slow-running code quickly), asmjit (you'll generate each machine instruction explicitly, and it is your responsibility to emit fast x86-64 code), GCCJIT or LLVM (both are above existing compilers so can be used to emit -a bit slowly- some optimized code). On POSIX & Linux systems, you could also emit some C code in some temporary file /tmp/tempcode.c
, fork a compilation (e.g. gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so
) of that code into a plugin, and dynamically load that generated plugin using dlopen(3) & dlsym(3)..
顺便说一句,我们不知道您正在编写的实际应用程序是什么,但您可以考虑在其中嵌入一些解释器,例如Lua or Guile。然后,您将使用并提供对嵌入式 evaluator/interpreter.
的回调我要在这里变得很老套,所以抓紧你的马裤。
标准 C api 带有两个函数,分别是 setjmp
和 longjmp
。除了糟糕的命名,他们实际上所做的是将当前状态(包括堆栈位置和寄存器值)的副本存储到 jmp_buf
(或者,技术名称,continuation
)。
现在,假设您创建了一个函数:
jmp_buf jb;
void sfunc(void) {
void *sp_minus1 = 0xBEEFBABE;
setjmp(jb);
}
当您调用 sfunc 时,将创建一个堆栈帧。因为此函数没有参数,堆栈中的第一个条目将是 return 地址,紧随其后的是 sp_minus1 对象。
为什么这很重要?那么,sp_minus1 的地址是相对于栈帧的开始的。如果你能在 jb
中找到堆栈帧的地址,你可以更改它...比如说,到堆中的某个位置?
此时我们得到的是一种为堆上的 longjmp 函数调用创建堆栈帧的方法,它可以包含有关调用它们的上下文的附加状态;或者换句话说,闭包。
我认为我从未见过任何人以这种方式使用 longjmp/setjmp,但是如果您正在寻找一种在 C 中动态生成和 return 函数的方法,我认为这将是您的最佳路线。
编辑:
下面是我描述的黑客攻击的实施示例:
#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 会生成导致程序中止的堆栈金丝雀。如果您使用“-fno-stack-protection”进行编译,它会起作用。
所以你想要一个函数 return 指向函数的指针。
double retfunc()
{
return 0.5;
}
double (*fucnt)()
{
return retfunc;
}
main()
{
printf("%f\n", (*funct())());
}
由于有些人显然偏执于编写 hack 来解决这个问题,这里有一个不太 hacky 的方法:使用带有 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 将 return 值为 0,否则它将 return longjmp 传递的值(例如 longjmp(jb , 5) 将导致 setjmp 为 return 5).
所以,我们正在做的是让我们的函数 return 指向它分配的数据结构的指针;然后像这样调用我们的闭包:
void *data = myfunc();
longjmp(jb, (int)(intptr_t)data);
请注意,不能保证 int 的大小足以在所有平台上存储指针;因此您可能需要创建一个数据池,并 return/pass 按句柄(池中的索引)处理数据。
正如我之前所说,闭包只是一个函数,它的所有数据都分配在堆上。
多年来,我一直在为 N64 和 PSP 游戏编写 hack。声称这是不可能的人可能从来没有修补过这类东西。主要归结为缺乏经验。