在以下代码段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;
}
答案 0 :(得分:3)
compare
从main()
调用,不传递任何参数。
没有。它只是从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
size
个base
字节元素,指向与key
指向的元素匹配的元素}。compar
指向的函数用于决定两个元素是否匹配。
compar
函数的指针传递给lfind
。然后,lfind
将分别使用compar
和base
作为key
和arg1
在内部拨打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