如何创建一个返回函数的函数?

时间:2016-06-11 19:15:03

标签: c function pointers modularity

大图:我有一个带有功能的模块和一个带有这些功能的程序和功能的模块。

当我组合两个函数时(来自函数的模块接口):

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”作为输入。

7 个答案:

答案 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::functionlambda-expressions进行了关闭。大多数其他编程语言(Ocaml,Haskell,Javascript,Lisp,Clojure,Python,......)都有闭包。

由于C中缺少真正的闭包(“数学上”C函数中唯一的闭合值是全局变量或静态变量或文字),大多数接受C函数指针的库都为某些客户端提供API处理callbacks数据(一个简单的例子可能是qsort_r,但更严肃地看一下GTK)。客户端数据(通常是不透明指针)可用于保持闭合值。您可能希望遵循类似的约定(因此系统地将函数指针作为带有一些额外客户端数据的回调传递),因此您需要更改C函数的签名(而不是只传递原始函数指针,您将通过函数指针和一些客户端数据作为回调,以“模拟”闭包。)

有时您可以在运行时生成C函数(使用非标准功能,可能借助操作系统或某些外部库)。您可能会使用JIT compilingGNU lightning这样的libjit库(两者都会快速生成一些运行缓慢的代码),asmjit(您将明确生成每台机器指令,并且你有责任发出快速的x86-64代码,GCCJITLLVM(两者都在现有的编译器之上,因此可以用来缓慢地发出一些优化的代码)。在POSIX&amp; Linux系统,你也可以在一些临时文件/tmp/tempcode.c中发出一些C代码,将该代码的编译(例如gcc -fPIC -Wall -O2 -shared /tmp/tempcode.c -o /tmp/tempcode.so)分成一个插件,并使用dlopen(3)动态加载生成的插件&安培; dlsym(3) ..

顺便说一下,我们不知道您编写的实际应用程序是什么,但您可能会考虑在其中嵌入一些解​​释器,例如: LuaGuile。然后,您将使用并向嵌入式评估程序/解释程序提供回调。

答案 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所说的可能,更不用说怎么做了

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

  1. 分配内存页
  2. 执行您想要的数据和机器代码,调用指针传递的函数等。
  3. 将内存保护从读/写更改为读/执行
  4. 返回指向已创建函数的指针
  5. 不用担心您每个功能需要4kB
  6. 可能存在某些库,但必然不可移植

答案 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带有一个名为setjmplongjmp的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;进行编译,它就可以正常工作。