函数调用分析
前置知识:
- 全局变量:在函数内部定义的变量
- 局部变量:在函数外部定义的变量
- esp:存储当前函数栈底的地址
- ebp:存储当前函数栈顶的地址
对于函数形参(实际上):
- 简单:cpu寄存器中
- 复杂:栈中开空间
函数调用机制:
- 局部变量占用的内存是在程序执行过程中“动态”地建立和释放的。这种“动态”是通过栈,由系统自动管理进行的,当任何一个函数调用发生时,系统都要做以下工作:
- 建立栈帧空间
- 保护现场:主调函数运行状态和返回地址入栈
- 为被调函数传递数据(进行实参和形参的结合),同时形参获得存储空间,接着给局部变量分配空间
- 执行被调函数函数体
- 当被调函数执行完成,释放被调函数中局部变量占用的栈空间
- 恢复现场:取主调函数运行状态及返回地址,释放栈帧空间
- 继续主调函数后续语句
以下面函数调用为例,进行分析:
#include <stdio.h>
#include <iostream>
using namespace std;
int sum(int a, int b)
{
int temp = 0;
temp = a + b;
return temp;
}
int main(void)
{
int a = 10;
int b = 20;
int ret = sum(a, b);
cout << "ret:" << ret << endl;
return 0;
}
步骤:
-
为
main
函数开辟栈帧空间,执行下面两条代码,为局部变量a
和b
分配内存int a = 10; // mov dword ptr[ebp-4], 0Ah int b = 20; // mov dword ptr[ebp-8], 14h
-
执行
int ret = sum(a, b);
,先在栈中为局部变量ret
分配空间,然后从右向左将参数压入栈中,即为sum
函数形参变量分配内存空间(所以不是进入sum
函数后再为形参变量分配空间),转换为汇编指令如下:mov eax, dword ptr[ebp-8] ;dword ptr[ebp-8]为main函数中变量b的值 push eax mov eax, dword ptr[ebp-4] ;dword ptr[ebp-4]为main函数中变量a的值 push eax
-
进入
sum
函数之前,保存下一条指令的地址call sum ; 将下一行指令的地址入栈 ; 以下为sum函数返回后要执行的指令 add esp, 8 ;地址为0x08124458,将这行指令的地址入栈(作用后面会分析) mov dword ptr[ebp-0Ch], eax
-
进入
sum
函数,执行sum
函数左大括号与第一行语句之间的指令(并不是直接就执行第一行语句),有如下指令:-
保存主调函数
main
函数的栈底地址push ebp ;将当前的ebp指向的地址入栈
-
移动
ebp
指向sum
函数的栈底(即指向当前函数的栈底)mov ebp, esp sub esp, 4Ch ;为sum函数开辟栈帧空间,esp指向栈顶,4Ch是用于举例
-
如果是window编译器下,可能还有一行指令为新开辟的栈帧初始化(gcc和g++不会初始化):
rep stos ;相当于for循环,将新开辟的栈帧空间的值,置为0xCCCCCCCC
-
定义一个局部变量,如果没有赋值,打印结果如下(如果允许打印,可能报错):
int a; cout << a << endl; // -858993460,即0xCCCCCCCC
-
-
-
执行
sum
函数的函数体int temp = 0; temp = a + b; return temp;
换成汇编指令
mov dword ptr[ebp-4], 0 ;为temp变量分配内存 mov eax, dword ptr[ebp+0Ch] ;取出a的值存入eax寄存器 add eax, dword ptr[ebp+8] ;执行a+b,并将结果存入eax寄存器 mov dword ptr[ebp-4], eax ;取出eax寄存器中的值,存入temp变量中 mov eax,dword ptr[ebp-4] ;再将temp变量的值存入eax寄存器中
-
执行
sum
函数最后一条语句与右大括号之间的指令-
回退栈帧,释放栈空间
mov esp, ebp ;回退栈帧
-
虽然回退栈帧,并没有清理栈空间中保存的值,可以写出以下代码取得变量的值,但是这是非常危险的行为,所以不要返回局部变量的地址
int *func() { int data = 10; return &data; // 不安全 } int *p = func(); cout << *p << endl; // 10,能正确打印 // 在中间调用了其他函数,就有可能会覆盖刚刚的栈空间 int *p = func(); func2(); cout << *p << endl; // 返回未知的值
-
将当前栈顶元素0x0018ff40出栈,并赋值给
ebp
, 相当于ebp
回到指向主调函数main
函数的栈底pop ebp
-
将当前栈顶元素0x8124458出栈,并将值存入CPU的PC寄存器(用于存放下一条执行指令)里面,此时已经回到
main
函数的栈空间ret
-
-
执行pc寄存器中保存的指令,即执行进入
sum
函数时保存的下一行指令add esp, 8 ;释放形参变量的内存空间
-
将
sum
函数返回时,保存在eax
寄存器中的值赋值给ret
变量mov dword ptr[ebp-0Ch], eax
标签:汇编,函数,int,sum,c++,eax,ebp,函数调用,ptr From: https://www.cnblogs.com/General-xd/p/18339588补充(不同数据大小使用不同的寄存器):
- <= 4 eax
- >4 && <= 8 eax edx
- >8 寄存器不够用,产生临时量带出返回值