【问题标题】:Function Pointer Memory Explanation in CC中的函数指针内存解释
【发布时间】:2013-08-19 11:12:27
【问题描述】:
#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. 在流​​水线分支预测处理器中运行代码时,直接调用函数或使用函数指针有什么区别?

【问题讨论】:

标签: c stack function-pointers


【解决方案1】:

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

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。

【讨论】:

  • 感谢您的努力,它对我的​​理解帮助很大
【解决方案2】:
  1. 函数指针包含程序文本段中函数的起始地址。
  2. 一旦调用,无论是直接调用还是通过指向函数的指针调用,函数都会以相同的方式运行。
  3. 我不确定。通常,不会有太大区别。指向函数的指针不会经常改变,如果有的话(例如,因为你动态加载了一个共享库,所以你必须使用指向函数的指针来调用函数)。

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

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

【讨论】:

  • “函数开始”是指函数调用/帧的“字节码数组”的第 0 个索引吗?
  • 字节码通常不是与 C 一起使用的术语。函数的开头是调用函数时程序跳转到的地址。它通常是序言代码,用于调整变量的存储位置等,这大致就是调用框架。
  • 当然,C 中没有这样的东西。我只是想可视化/具体化函数指针指向的“地址”东西。我认为在任何情况下,流程都必须对传入的函数帧/调用进行一些初始化(并可能将 PC 设置为底层函数指针的值),然后再继续。
【解决方案3】:
  1. 是的
  2. 绝对
  3. 没有区别

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

【讨论】:

  • 我不能绝对确定3。如果指针的值经常以(对于处理器)不可预测的方式变化,那么分支预测在更多情况下可能是错误的。
  • 非常感谢您的帮助
【解决方案4】:

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-26
  • 2022-09-27
  • 1970-01-01
  • 2013-04-14
  • 1970-01-01
  • 2017-03-01
  • 2016-03-06
相关资源
最近更新 更多