如何将参数传递给 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
函数的指针被传递给lfind
。 lfind
将在内部调用 compar
,分别使用 base
和 key
作为 arg1
和 arg2
。
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
内部,该代码必须以定义的方式调用传入的函数。
在下面的代码片段 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 frommain()
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 ofsize
bytes pointed to bybase
for an element which matches the one pointed to bykey
. The function pointed to bycompar
is used decide whether two elements match.
指向compar
函数的指针被传递给lfind
。 lfind
将在内部调用 compar
,分别使用 base
和 key
作为 arg1
和 arg2
。
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
内部,该代码必须以定义的方式调用传入的函数。