#include <stdio.h>
#include <stdlib.h>
int (*fptr1)(int);
int square(int num){
return num*num;
}
void main(){
fptr1 = □
printf("%d\n",fptr1(5));
}
当我们调用函数指针时,有人可以简要解释堆栈中发生的事情吗?直接在main()中调用函数和通过C语言中的函数指针通过物理内存和进程调用它有什么区别?
当我们用函数指针调用函数时,我试图理解内存中发生了什么,但这对我来说还不够。
答案 0 :(得分:10)
回答这个问题的最好方法是查看反汇编(稍加修改的样本):
fptr1 = □
int result1 = fptr1(5);
int result2 = square(5);
此x64 asm中的结果:
fptr1 = □
000000013FA31A61 lea rax,[square (013FA31037h)]
000000013FA31A68 mov qword ptr [fptr1 (013FA40290h)],rax
int result1 = fptr1(5);
000000013FA31A6F mov ecx,5
000000013FA31A74 call qword ptr [fptr1 (013FA40290h)]
000000013FA31A7A mov dword ptr [result1],eax
int result2 = square(5);
000000013FA31A7E mov ecx,5
000000013FA31A83 call square (013FA31037h)
000000013FA31A88 mov dword ptr [result2],eax
正如您所看到的,直接调用函数和通过指针调用程序集几乎相同。在这两种情况下,CPU都需要访问代码所在的位置并调用它。直接调用的好处是不必取消引用指针(因为偏移将被烘焙到程序集中)。
编辑:如果我们要将分支插入到上面的示例中,那么用尽有趣的场景就不会花费很长时间,所以我将在这里解决它们:
如果我们在加载(或赋值)函数指针之前有一个分支,例如(在伪程序集中):
branch zero foobar
lea square
call ptr
然后我们可以有所不同。假设管道选择加载并开始处理foobar
处的指令,然后当它意识到我们实际上不会采用该分支时,它必须停止以加载函数指针和解除引用它。如果我们只是叫一个知道地址,那么就不会有失速。
案例二:
lea square
branch zero foobar
call ptr
在这种情况下,直接调用与通过函数指针之间没有任何区别,因为我们需要的一切都已知道处理器是否开始执行错误的路径,然后重置以开始执行调用。
第三种情况是分支跟随调用,从管道的角度来看,这显然不是很有趣,因为我们已经执行了子程序。
所以要完全重新回答问题 3 ,我会说是的,有区别。但真正的问题是编译器/优化器是否足够智能以在函数指针赋值后移动分支,因此它属于案例2而不是案例1。
答案 1 :(得分:5)
直接在main()中调用函数并通过函数指针调用函数有什么区别?
唯一的区别可能是从内存中获取函数指针的额外内存引用。
答案 2 :(得分:3)
通过函数指针调用函数只是一种方便;并且可以将函数作为参数传递给另一个函数。例如,参见qsort()
答案 3 :(得分:2)
通过名称调用函数和通过函数指针调用函数没有区别。函数指针的目的是让你可以调用在其他地方指定的函数。例如,qsort()
函数将函数指针作为一个参数,它调用它来确定如何对数组的元素进行排序,而不是只有一个比较方法。