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

时间:2015-04-26 05:37:25

标签: c function-pointers

在以下代码段Reference中,compare调用main(),但未传递任何参数。 我假设它将((char *)&key, (char *)string)作为函数调用所需的两个参数。但它如何在C内部工作?编译比较时编译器是否填写参数?

#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?

4 个答案:

答案 0 :(得分:3)

  

comparemain()调用,不传递任何参数。

没有。它只是从main引用的。

基本上,它会告诉lfind&#34;嘿,如果你想比较条目,可以使用这个功能 - 它可以按你期望的方式调用。&#34;

然后,

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    $42, 4(%esp)
    jmp *%eax
    .cfi_endproc

该函数将给定的函数指针转换为%eax,然后将42置于预期位置,然后调用%eax。 (相反,当我激活优化时,跳转那里。逻辑保持不变。)

该函数不知道要调用哪个函数,但如何调用

indirect0:
.LFB14:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    $0, 4(%esp)
    jmp *%eax
    .cfi_endproc

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

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

    movl    $33, (%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相同。

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

答案 1 :(得分:1)

没有魔力。您实际上并未从main调用它,当您致电lfind时,您只是将引用传递给它。编译器允许你这样做,因为它已经知道(a)在那里期望什么样的功能(即,使用那两个args)和(b)知道你正在使用的那个匹配正确,所以没有抱怨。如果您以不同方式(错误地)定义compare,则无法编译。

lfind内,它直接使用两个参数按名称调用。当你第一次看到它们时,像这样的函数指针似乎很滑,但是在C中它们实际上很费力地显式 - 你必须传入一个与lfind&#39;中声明的函数签名相匹配的函数的引用。声明。在lfind内部,该代码必须以定义的方式调用传入函数。

答案 2 :(得分:0)

通过按下堆栈上的参数并进行子程序调用,函数指针的工作方式与常规函数调用完全相同。唯一的区别是子程序调用的位置,带有函数指针,位置是从给定变量加载的,而直接函数调用,编译器静态生成的位置是从加载代码块的位置开始的偏移量

理解这一点的最佳方法实际上是使用GDB并查看编译器发出的指令。尝试使用不太复杂的代码示例,然后从那里开始工作。从长远来看,您将节省时间。

答案 3 :(得分:0)

lfind的语法是

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

lfind函数在数组中搜索*nmemb sizebase字节元素,指向与key指向的元素匹配的元素}。 compar指向的函数用于决定两个元素是否匹配。

compar函数的指针传递给lfind。然后,lfind将分别使用comparbase作为keyarg1在内部拨打arg2

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

#include <stdlib.h>

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