5
#include <stdio.h>
#include <stdlib.h>

int (*fptr1)(int);

int square(int num){
  return num*num;
}

void main(){
  fptr1 = &square;
  printf("%d\n",fptr1(5));
}

有人可以简单解释一下当我们调用函数指针时堆栈中会发生什么吗?在 main() 中直接调用函数和 C 语言中通过函数指针通过物理内存和进程的方式调用有什么区别?

我试图了解当我们使用函数指针调用函数时内存中会发生什么,但这对我来说还不够。

  1. 当我们通过指针调用函数时,指针是否在代码空间中具有该函数的位置?
  2. 当被调用函数运行时,它与 main() 中的正常调用函数相同吗?
  3. 当代码在流水线分支预测处理器中运行时,直接调用函数或使用函数指针有什么区别?
4

4 回答 4

10

回答这个问题的最好方法是查看反汇编(稍作修改的示例):

fptr1 = &square;
int result1 = fptr1(5);
int result2 = square(5);

这个 x64 asm 的结果:

    fptr1 = &square;
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 都需要访问代码所在的位置并调用它。直接调用的好处是不必取消引用指针(因为偏移量将被烘焙到程序集中)。

  1. 是的,您可以在函数指针的分配中看到,它存储了“square”函数的代码地址。
  2. 从堆栈设置/拆卸:是的。从性能的角度来看,如上所述,存在细微差别。
  3. 没有分支,所以这里没有区别。

编辑:如果我们要在上面的示例中插入分支,用完有趣的场景不会花费很长时间,所以我将在这里解决它们:

如果我们在加载(或赋值)函数指针之前有一个分支,例如(在伪汇编中):

branch zero foobar
lea square
call ptr

那我们就可以有所不同了。假设管道选择在 处加载并开始处理指令foobar,那么当它意识到我们实际上并不打算采用该分支时,它必须停止以加载函数指针并取消引用它。如果我们只是呼叫一个已知地址,那么就不会有一个摊位。

案例二:

lea square
branch zero foobar
call ptr

在这种情况下,直接调用与通过函数指针调用之间没有任何区别,因为我们需要的一切都已经知道处理器是否开始沿着错误的路径执行然后重置以开始执行调用。

第三种情况是分支跟随调用,从管道的角度来看,这显然不是很有趣,因为我们已经执行了子例程。

因此,要完全重新回答问题3,我会说是的,这是有区别的。但真正的问题是编译器/优化器是否足够聪明,可以在函数指针分配移动分支,所以它属于案例 2 而不是案例 1。

于 2013-08-16T23:51:56.753 回答
5
  1. 指向函数的指针包含程序文本段中函数的开始地址。
  2. 一旦被调用,无论是直接调用还是通过指向函数的指针调用,该函数都会以相同的方式运行。
  3. 我不确定。通常,不会有太大区别。指向函数的指针不会经常改变,如果有的话(例如,因为你动态加载了一个共享库,所以你必须使用指向函数的指针来调用函数)。

直接在 main() 中调用函数和通过函数指针调用有什么区别?

唯一的区别可能是额外的内存引用来从内存中获取函数指针。

于 2013-08-16T23:47:10.077 回答
3
  1. 是的
  2. 绝对地
  3. 没有不同

通过函数指针调用函数只是为了方便;并且可以将函数作为参数传递给另一个函数。例如,请参见 qsort()

于 2013-08-16T23:46:56.723 回答
2

通过函数名调用函数和通过函数指针调用函数没有区别。函数指针的目的是让您可以调用在其他地方指定的函数。例如,该qsort()函数将函数指针作为参数,它调用该函数来确定如何对数组元素进行排序,而不是只有一种比较方法。

于 2013-08-16T23:47:04.357 回答