如何将参数传递给 C 中的函数指针?

How are arguments passed to function pointers in C?

在下面的代码片段 Reference 中,compare 是从 main() 调用的,没有传递任何参数。 我假设它以 ((char *)&key, (char *)string) 作为函数调用所需的两个参数。但是它在 C 内部是如何工作的呢?调用compare时编译器会填入参数吗?

#include <search.h>
#include <string.h>
#include <stdio.h>

#define  CNT           2

int compare(const void *arg1,const void *arg2)
{
   return (strncmp(*(char **)arg1, *(char **)arg2, strlen(*(char **)arg1)));
}

int main(void)
{
   char **result;
   char *key = "PATH";
   unsigned int num = CNT;
   char *string[CNT] =  {
      "PATH = d:\david\matthew\heather\ed\simon","LIB = PATH\abc" };

   /* The following statement finds the argument that starts with "PATH"         */

   if ((result = (char **)lfind((char *)&key, (char *)string, &num,
                  sizeof(char *), compare)) != NULL)
      printf("%s found\n", *result);
   else
      printf("PATH not found \n");
   return 0;


}

How do function pointers in C work?

函数指针的工作方式与常规函数调用完全相同,方法是将参数压入堆栈并进行子例程调用。唯一的区别是子程序调用的位置,使用函数指针,该位置从给定变量加载,而对于直接函数调用,该位置由编译器静态生成,作为代码块加载到的位置的偏移量.

理解这一点的最好方法是实际使用 GDB 并查看编译器发出的指令。用一个不太复杂的代码示例来尝试它,然后从那里开始工作。您将在漫长的 运行.

中节省时间

compare is called from main() without any parameters being passed.

没有。它只是从 main.

中引用的

本质上,它告诉lfind "Hey, if you want to compare entries, take this function - it can be called exactly the way you expect it."

lfind,然后,知道了这一点,并且每当它需要比较两个条目时,它都会将参数放在正常调用中会放置它们的任何地方(在堆栈上,正确的寄存器中或任何地方,取决于体系结构的调用约定)并执行对给定地址的调用。这称为间接调用,通常比直接调用要贵一些。

让我们换一个更简单的例子:

#include <stdio.h>

int add1(int x) {
    return x + 1;
}

int times2(int x) {
    return x * 2;
}

int indirect42(int(*f)(int)) {
    // Call the function we are passed with 42 and return the result.
    return f(42);
}

int indirect0(int(*f)(int)) {
    // Call the function we are passed with 0 and return the result.
    return f(0);
}

int main() {
    printf("%d\n", add1(33));
    printf("%d\n", indirect42(add1));
    printf("%d\n", indirect0(add1));

    printf("%d\n", times2(33));
    printf("%d\n", indirect42(times2));
    printf("%d\n", indirect0(times2));
    return 0;
}

在这里,我首先自己调用了函数 add1(),然后我告诉另外两个函数根据自己的目的使用该函数。然后我用另一个函数做同样的事情。

这很完美 - 我用 33 调用了两个函数,然后用 42 调用了 0。结果 - 第一个函数为 34、43 和 1,第二个函数为 66、84 和 0 - 匹配期望。

如果我们看一下 x86 汇编器输出,我们会看到

…
indirect42:
.LFB13:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    , 4(%esp)
    jmp *%eax
    .cfi_endproc

该函数将给定的函数指针放入 %eax,然后将 42 放在预期的位置,然后调用 %eax。 (相应地,当我激活优化时,它 跳到 那里。逻辑保持不变。)

函数不知道调用哪个函数,但是如何调用。

indirect0:
.LFB14:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    [=12=], 4(%esp)
    jmp *%eax
    .cfi_endproc

该函数与另一个函数的作用相同,但它将 0 传递给它获得的任何函数。

然后,在调用所有这些东西时,我们得到

    movl    , (%esp)
    call    add1
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

这里我们有一个正常的函数调用。

    movl    $add1, (%esp)
    call    indirect42
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

在这里,add1 的地址被压入堆栈并提供给 indirect42,以便函数可以随意使用它。

    movl    $add1, (%esp)
    call    indirect0
    movl    %eax, 4(%esp)
    movl    $.LC0, (%esp)
    call    printf

indirect0相同。

(其他东西剪掉,因为它的工作原理相同)

lfind 的语法是

void *lfind (const void *key, const void *base, size_t *nmemb, size_t size, comparison_fn_t compar)  

The lfind function searches in the array with *nmemb elements of size bytes pointed to by base for an element which matches the one pointed to by key. The function pointed to by compar is used decide whether two elements match.

指向compar函数的指针被传递给lfindlfind 将在内部调用 compar,分别使用 basekey 作为 arg1arg2

GNU Libc 在 stdlib.h if _GNU_SOURCE 集中定义了 comparison_fn_t

#include <stdlib.h>

#ifndef HAVE_COMPARISON_FN_T
typedef int (*comparison_fn_t)(const void *, const void *);
#endif

没有魔法。您实际上并不是从 main 调用它,您只是在调用 lfind 时传递了一个 reference 给它。编译器允许您这样做,因为它已经知道 (a) 那里需要什么样的函数(即,使用这两个参数)和 (b) 知道您使用的函数正确匹配,所以没有抱怨。如果您以不同方式(错误地)定义了 compare,它就不会编译。

lfind内部,直接用两个参数按名称调用。像这样的函数指针,当您第一次看到它们时,似乎很狡猾,但在 C 中,它们实际上是非常显式的——您必须传递一个对与 lfind 声明中声明的函数签名相匹配的函数的引用。在 lfind 内部,该代码必须以定义的方式调用传入的函数。