简体   繁体   English

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

[英]How are arguments passed to function pointers in C?

In the following code snippet Reference , compare is called from main() without any parameters being passed. 在下面的代码片段Reference中 ,从main()调用compare而不传递任何参数。 I assume it is taking ((char *)&key, (char *)string) as the two parameters needed for the function call. 我假设它将((char *)&key, (char *)string)作为函数调用所需的两个参数。 But how does it work internally in C? 但是它在C语言内部如何工作? Does the compiler fill in the arguments when compare is called? 调用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? C中的函数指针如何工作?

compare is called from main() without any parameters being passed. main()调用compare而不传递任何参数。

No. It is just referred to from main . 不。它只是从main引用的。

Essentially, it tells lfind "Hey, if you want to compare entries, take this function - it can be called exactly the way you expect it." 从本质上讲,它告诉lfind “嘿,如果您想比较条目,请使用此功能-可以按照您期望的方式调用它。”

lfind , then, knows about that and whenever it needs to compare two entries, it puts the arguments wherever it would put them on a normal call (on the stack, into the right registers or wherever, depending on the calling conventions of the architecture) and performs the call to the given address. 然后, lfind知道这一点,并且每当需要比较两个条目时,就会将参数放在可以进行常规调用的位置(在堆栈上,放到正确的寄存器中,或者在任何位置,取决于体系结构的调用约定)。并执行到给定地址的呼叫。 This is called an indirect call and is usually a bit more expensive than a direct call. 这称为间接调用,通常比直接调用贵一些。

Let's switch to a simpler example: 让我们转到一个更简单的示例:

#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;
}

Here, I call the function add1() on my own first, then I tell two other functions to use that function for their own purpose. 在这里,我首先自己调用函数add1() ,然后告诉其他两个函数将其用于自己的目的。 Then I do the same with another function. 然后,我对另一个功能执行相同的操作。

And this works perfectly - both functions are called with 33 by me, then with 42 and with 0. And the results - 34, 43 and 1 for the first and 66, 84 and 0 for the 2nd function - match the expectations. 这样做非常好-我分别用33和42以及0调用了两个函数。结果-第一个函数的34、43和1,第二个函数的结果分别为66、84和0-符合期望。

If we have a look at the x86 assembler output, we see 如果我们看一下x86汇编器的输出,我们会看到

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

The function gets the given function pointer into %eax , then puts the 42 where it is expected and then calls %eax . 该函数将给定的函数指针放入%eax ,然后将42放在期望的位置,然后调用%eax (Resp., as I activated optimization, it jumps there. The logic remains the same.) (或者,当我激活优化时,它会跳到那里。逻辑保持不变。)

The function doesn't know which function is to be called, but how it is to be called. 该函数不知道要调用哪个函数,但是不知道如何调用它。

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

The function does the same as the other one, but it passes 0 to whatever function it gets. 该函数与另一个函数相同,但是它将0传递给它得到的任何函数。

Then, as it comes to calling all this stuff, we get 然后,谈到所有这些东西,我们得到

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

Here we have a normal function call. 在这里,我们有一个正常的函数调用。

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

Here, the address of add1 is pushed to the stack and given to indirect42 so that function can do with it as it wants. 在这里,将add1的地址压入堆栈并提供给indirect42以便函数可以根据需要对其进行处理。

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

The same with indirect0 . indirect0相同。

(other stuff snipped as it works the same) (其他内容因其工作原理而被删除)

There's no magic. 没有魔术。 You're not actually calling it from main , you're just passing along a reference to it when you call lfind . 您实际上并不是从main调用它,而是在调用lfind时传递对它的引用 The compiler lets you do this because it already knows (a) what kind of function is expected there (ie, with those two args) and (b) knows that the one you're using matches correctly, so no complaints. 编译器允许您执行此操作,因为它已经知道(a)那里期望使用哪种函数(即具有这两个args),并且(b)知道您正在使用的函数正确匹配,因此不会有任何抱怨。 If you'd defined compare differently (wrongly), it wouldn't have compiled. 如果您定义的compare不同(错误),则不会进行编译。

Inside of lfind , it's invoked directly with the two parameters by name. lfind内部,直接使用名称的两个参数来调用它。 Function pointers like this seem slippery when you first see them, but in C they're actually laboriously explicit-- you must pass in a reference to a function that matches the function signature declared in lfind 's declaration. 当您初次看到它们时,这样的函数指针似乎很滑,但是在C语言中它们实际上是很费力的-您必须传递对与lfind声明中声明的函数签名匹配的函数的引用。 And inside of lfind , that code must call the passed-in function in the defined way. lfind内部,该代码必须以定义的方式调用传入的函数。

Function pointers work exactly the same as regular function calls, by pushing the parameters on the stack and making a subroutine call. 通过将参数压入堆栈并进行子例程调用,函数指针的工作原理与常规函数调用完全相同。 The only difference is the location of the subroutine call, with a function pointer, the location is loaded from the given variable whereas with a direct function call, the location is statically generated by the compiler as an offset from wherever the code block is loaded to. 唯一的区别是子例程调用的位置,通过函数指针,该位置是从给定变量加载的,而对于直接函数调用,该位置是由编译器静态生成的,它是代码块加载到的位置的偏移量。 。

The best way to understand this is actually using GDB and look at the instructions emitted by the compiler. 理解这一点的最佳方法实际上是使用GDB并查看编译器发出的指令。 Try it with a less complex code example, then work up from there. 尝试一个不太复杂的代码示例,然后从那里开始工作。 You will save time in the long run. 从长远来看,您将节省时间。

Syntax for lfind is 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 . 所述lfind与阵列中函数搜索*nmemb元素size字节指向base为相匹配由所述一个指向的元素key The function pointed to by compar is used decide whether two elements match. 该功能指向compar用来决定两个元素是否匹配。

Pointer to compar function is passed to lfind . compar函数的指针传递给lfind lfind will then call compar internally using base and key as arg1 and arg2 , respectively. lfind然后将compar使用basekey作为arg1arg2内部调用compar

GNU Libc defines comparison_fn_t in stdlib.h if _GNU_SOURCE set. if _GNU_SOURCE设置了if _GNU_SOURCEif _GNU_SOURCE GNU Libc在stdlib.h定义了comparison_fn_t

#include <stdlib.h>

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM