你能在 C 中拥有带指定参数值的函数指针吗?

Can you have function pointer with assigned parameter value in C?

我想指向类似add(2, 1)的东西,然后调用它而不需要指定参数。有什么方法可以用函数指针做到这一点吗?

int add(int a, int b) { return a + b; }

不,您不能拥有指向 add 并为其分配参数值的指针。但是你可以这样做:

int add_2_1() { return add(2, 1); }

但是我看不出这有什么用...

虽然简短的回答是 "no",但有多种方法可以完成您想要做的事情。

首先要记住的是add需要两个参数。不管你做什么,你都必须给它提供这两个参数。

要记住的第二件事是,如果你想调用最终调用 add(2, 1) 的函数,你必须在某处存储或硬编码值 21以便可以使用它们。

我能想到几种方法。

  1. 创建一个调用 add(2, 1) 的包装函数并让您的代码调用包装函数。

    int add2And1()
    {
       return add(2, 1);
    }
    

    然后在通话中使用 add2And1

  2. 创建依赖全局数据的包装函数。在使用包装函数之前先设置全局数据。

    int param1 = 0;
    int param2 = 0;
    
    
    int addParams()
    {
       return add(param1, param2);
    }
    

    然后使用:

    param1 = 2;
    param2 = 1;
    

    在您的代码中调用 addParams.

GCC 扩展可让您执行此操作:

int func(int addend, int (*next)(int (*f2)(int))
{
    int add(int addend2) { return addend + addend2; }
    next(add);
}

但是,如果您尝试这样做:

int (*)(int) func(int addend)
{
    int add(int addend2) { return addend + addend2; }
    return add;
}

无法使用该函数,因为 func(3)(3) 在已释放的堆栈上执行了一个 trampoline*。这几乎是最未定义的行为。

*蹦床是一小段代码立即跳转到另一段代码。在这种情况下,蹦床看起来像

mov rax, 0xfuncstackframeaddress
mov r10, rax
lea rax, [func.add]
jmp rax

当然有很多方法可以做到这一点,GCC 使用哪一个并不重要。 GCC 正在解决如何通过编写动态代码来传递带有函数指针的参数的问题。不管那里写了什么代码,下次函数调用深度足够深时,堆栈帧将被覆盖。

作为对您问题的评论,Basile Starynkevitch suggested reading about closures。闭包的核心是将要调用的函数与某些上下文结合起来,在文献中称为函数的 environment.

Object-oriented 语言有一个类似的概念,称为 delegates,其中特定实例可以成为以后调用的环境,调用站点不直接了解底层对象。本机支持闭包的语言会自动捕获或“关闭”环境中提供的绑定。它可能看起来是一种奇怪的语言功能,但正如您问题背后的动机所暗示的那样,它可以是富有表现力和有用的。

下面是 C 程序中闭包概念的一个简单示例,其中由程序员明确指示捕获。

你想要最终调用的函数加上一些前面的内容是

#include <stdio.h>

int
add_two_numbers(int a, int b)
{
  return a+b;
}

闭包是结合其环境调用的函数。在 C 中,要调用的函数是一个 指向函数 的指针。请注意 f 的参数与 add_two_numbers.

的参数对齐
typedef struct {
  struct {  /* environment */
    int a;
    int b;
  } env;

  int (*f)(int, int);  /* function to be called */
} closure;

我们想创建一个闭包,,设置参数值的关联,以便在我们准备好时与要调用的函数一起传递。在这个简单的示例中,make_adder 将闭包分配 space 的问题留给了它的调用者。

void
make_adder(int a, int b, closure *c)
{
  c->env.a = a;
  c->env.b = b;
  c->f = add_two_numbers;
}

现在您已经知道如何创建我们的简单闭包之一,您可以调用或调用

int invoke_closure(closure *c)
{
  return c->f(c->env.a, c->env.b);
}

最后,用法看起来像

int main(void)
{
  closure c;
  make_adder(2, 1, &c);

  printf("The answer is %d.\n", invoke_closure(&c));

  return 0;
}

输出为

The answer is 3.

进一步阅读