C中的功能指针存储器说明

cyc*_*rel 5 c stack function-pointers

#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));
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

jos*_*ley 10

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

fptr1 = &square;
int result1 = fptr1(5);
int result2 = square(5);
Run Code Online (Sandbox Code Playgroud)

结果在这个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  
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在直接调用函数和通过指针调用函数之前,程序集几乎完全相同.在这两种情况下,CPU都需要访问代码所在的位置并调用它.直接调用的好处是不必取消引用指针(因为偏移将被烘焙到程序集中).

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

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

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

branch zero foobar
lea square
call ptr
Run Code Online (Sandbox Code Playgroud)

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

案例二:

lea square
branch zero foobar
call ptr
Run Code Online (Sandbox Code Playgroud)

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

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

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


Jon*_*ler 5

  1. 指向函数的指针包含程序文本段中函数开头的地址.
  2. 一旦被调用,该函数运行相同,无论是直接调用还是通过指向函数的指针.
  3. 我不确定.通常,不会有太大差异; 如果有的话,指向函数的指针不会经常更改(例如,因为你动态加载了一个共享库,所以你必须使用一个指向函数的指针来调用该函数).

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

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