C语言函数调用过程原理

一 、预备知识

• 栈(Stack)

  栈是能够在函数运行之前自动分配足够的空间资源,函数运行完毕后自动回收资源。

• 堆(Heap)

  堆的空间资源不同于栈,想要获取必须由程序员手动申请,然后由操作系统根据一定的算法进行分配。操作系统只有在进程结束时会自动回收该进程对应的堆空间资源,不过最好由程序员手动释放资源。

• 栈帧(Stack Frame)

  栈帧的分配是从高地址向低地址逐步执行的。一个栈帧大小不是无限的,其最靠近低地址的一端称为栈顶,最接近高地址的一端称为栈底,栈顶地址和栈底地址各自保存在专门的寄存器里边,这两个专门的寄存器存放的值都是地址,故亦可分别称之为栈顶指针、栈底指针。
  一个栈帧栈底地址减去栈顶地址所得的值决定了该栈帧的大小,可以通过让栈顶指针自增与自减分别控制栈帧的缩小与扩大。
在这里插入图片描述
在这里插入图片描述
  要注意,栈的生长方向是从高地址到低地址的,栈是向下生长的pop操作后,栈中的数据并没有被清空,只是该数据我们无法直接访问
  我们一般将%ebp到%esp之间的区域当作栈帧。

• 堆栈指针寄存器和基址指针寄存器

  堆栈指针寄存器和基址指针寄存器都属于通用寄存器。
  堆栈指针寄存器用来存放栈帧的栈顶地址,根据数据位数不同可以分为三种,16位的 sp,32位的 esp,64位的 rsp,为说明方便,下文以 esp 为例进行阐述。
  基址指针寄存器用来存放栈帧的栈底地址,根据数据位数不同可以分为三种,16位的 bp,32位的 ebp,64位的 rbp,为说明方便,下文以 ebp 为例进行阐述。

• 指令寄存器 ip

  该寄存器总是存放下一条执行指令的所在地址。

• 函数调用指令 call

  调用一个函数时,一定会执行 call 指令,汇编中调用 printf 函数的写法如下。

call printf

  call 指令包括两个步骤,第一步是让当前指令寄存器 ip 的值入栈,作为返回地址,第二步是将指令寄存器 ip 的值修改为接下来即将调用的函数第一条机器指令的所在地址,从而实现跳转。

• 不同函数的机器指令段的共性

  每个函数的机器指令段的开头,都有以下几步操作:

  1.第一步,在栈帧中保存上一栈帧的栈底地址,汇编指令为 push ebp。
  2.第二步,将上一栈帧的栈顶地址作为当前函数栈帧的栈底地址,汇编指令为 mov ebp, esp。
  3.第三步,为当前函数的局部变量开辟足够的空间,汇编指令为 sub esp, M,M 为局部变量占用栈帧空间的字节数。

  每个函数的机器指令段的末尾,都有以下几步操作:

  第一步,将 esp 恢复为为局部变量开辟空间之前的值,汇编指令为 mov esp, ebp,恢复后,esp 的值恰好是上一栈帧栈底地址的地址。
  第二步,将 ebp 恢复为上一栈帧的栈底地址,汇编指令为 mov ebp, [esp],恢复后,esp 的值恰好是存放返回地址的地址。
  第三步,将 eip 恢复为 call 指令第一步骤所操作的值,汇编指令为 mov eip, [esp],恢复后,esp 的值恰好为刚执行完的函数的第一个形参的入栈地址。
  第四步,将 esp 值恢复为为刚执行完的函数的参数开辟空间之前的值,汇编指令为 pop …,恢复后,esp 的值恰好是当前栈帧最靠近 0 地址的局部变量的地址。

二 、 C 函数调用过程剖解

#include <stdio.h>

int main(void)
{
    int apple = 10;
    int pear = 20;
    int total = 0;
    printf("apple = %d, pear = %d.\n", apple, pear);
    total = apple + pear;
    return 0;
}

  1.printf 函数调用之前,参数从右向左入栈。
  2.调用 call 指令,此时存储在指令寄存器 ip 中的值是 printf 函数下一条语句 total = apple + pear; 对应的机器指令的地址,该地址入栈,同时指令寄存器 ip 的值修改为 printf 函数在代码段中的第一条指令的地址。
  3.开始执行 printf 函数时,会进行三步操作——在 printf 函数栈帧中保存 main 函数栈帧的栈底地址;将 main 函数栈帧的栈顶地址作为 printf 函数栈帧的栈底地址;为 printf 函数的局部变量开辟足够的空间。三步操作执行完之后便开始执行 printf 函数的主体机器指令段。
  3.printf 函数的主体机器指令段执行完毕后,便开始收尾工作——将 esp 恢复为为 printf 函数局部变量开辟空间之前的值;将 ebp 恢复为 main 函数栈帧的栈底地址;将 eip 恢复为语句 total = apple + pear; 对应的机器指令地址;将 esp 值恢复为为 printf 函数的参数开辟空间之前的值,恢复后,esp 的值恰好是 total 的地址。

参考:C函数调用过程原理及函数栈帧分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值