标签:二进制 自动分配 返回 函数参数 32位 自己的 操作系统 fun http
目录
在 C/C++ 函数调用的整个过程中内存空间进行了什么操作?本文对 C/C++ 函数调用原理进行扼要说明。
地址在内存中存放时可能会跨越连续若干个存储单元(一个存储单元的大小为 1 个字节),而每个存储单元都有自己的编号,这个编号称为地址。机器规定以最小的编号作为该数据的地址。
请看以下代码和图 1。
uint8_t a = 7;
uint16_t b = 500;
uint32_t c = 1000;
变量 a 占据编号为 24 的内存单元,变量 b 占据编号为 25~26 的内存单元,变量 c 占据编号为 27~30 的内存单元。
依据约定,变量 a,b,c 的地址分别为 24,25,27。
每个函数经过编译生成的二进制机器指令皆存储在内存空间中的代码段。
上文提到,栈能够为函数运行分配足够的空间资源,这种资源便称为栈帧。
堆栈指针寄存器和基址指针寄存器都属于通用寄存器。
该寄存器总是存放下一条执行指令的所在地址。
push 和 pop 都属于汇编指令。
call 也属于汇编指令。
调用一个函数时,一定会执行 call 指令,汇编中调用 printf 函数的写法如下。
call printf
call 指令包括两个步骤,第一步是让当前指令寄存器 ip 的值入栈,作为返回地址,第二步是将指令寄存器 ip 的值修改为接下来即将调用的函数第一条机器指令的所在地址,从而实现跳转。
函数参数入栈顺序为从右到左。
func(1, ‘A‘, 3.14);
该函数参数入栈顺序为 3.14
,‘A‘
,1
。
每个函数的机器指令段的开头,都有以下几步操作:
push ebp
。mov ebp, esp
。sub esp, M
,M 为局部变量占用栈帧空间的字节数。每个函数的机器指令段的末尾,都有以下几步操作:
mov esp, ebp
,恢复后,esp 的值恰好是上一栈帧栈底地址的地址。mov ebp, $esp
,恢复后,esp 的值恰好是存放返回地址的地址。mov eip, $esp
,恢复后,esp 的值恰好为刚执行完的函数的第一个形参的入栈地址。pop ...
,恢复后,esp 的值恰好是当前栈帧最靠近 0 地址的局部变量的地址。以下列程序为例。
#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;
}
printf
函数调用之前,参数从右向左入栈。printf
函数下一条语句 total = apple + pear;
对应的机器指令的地址,该地址入栈,同时指令寄存器 ip 的值修改为 printf
函数在代码段中的第一条指令的地址。printf
函数时,会进行三步操作——在 printf
函数栈帧中保存 main
函数栈帧的栈底地址;将 main
函数栈帧的栈顶地址作为 printf
函数栈帧的栈底地址;为 printf
函数的局部变量开辟足够的空间。三步操作执行完之后便开始执行 printf
函数的主体机器指令段。printf
函数的主体机器指令段执行完毕后,便开始收尾工作——将 esp 恢复为为 printf
函数局部变量开辟空间之前的值;将 ebp 恢复为 main
函数栈帧的栈底地址;将 eip 恢复为语句 total = apple + pear;
对应的机器指令地址;将 esp 值恢复为为 printf
函数的参数开辟空间之前的值,恢复后,esp 的值恰好是 total
的地址。标签:二进制 自动分配 返回 函数参数 32位 自己的 操作系统 fun http
原文地址:https://www.cnblogs.com/HubbardHuang/p/9900897.html