#include<stdio.h>
int sum(int a,int b)
{
int tmp = 0;
tmp = a+b;
return tmp;
}
int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = sum(a,b);
printf("ret = %d\n",ret);
return 0;
}
1、函数调用函数,其栈帧开辟及回退过程是什么?
2、跳到sum函数执行,中间是怎么做到的?
3、执行完sum函数后,如何知道回到main函数?回到mian函数哪里执行?
要想弄清楚这些问题,就需要看汇编语言,有inter x86 和AT&T (Unix)两种,大体是一样的,只是前者从右向左看,后者从左向右看。其大致过程如下:
用两个寄存器表示栈,esp保存main函数这个栈帧的栈顶的地址,ebp保存main函数这个栈帧的栈底的地址(内核调用main函数的地址)。也可以看成指针。我们画出栈示意图,下边是栈底,高地址。
局部变量通过下标访问,例如[ebp-4]、[ebp-8]等。首先从main函数开始,变量入栈。sum函数入栈参数从右向左入栈。因为C/C++支持可变参函数,从左向右看不到一共有多少个参数,必须得遍历,从右向左可以计数,知道共有多少个参数。
参数入栈时,eax和ecx分别记录形参,然后入栈。call指令第一步把下一行指令的地址先入栈,第二步jmp跳转到sum函数。
sum函数开始前的指令做什么?
先把调用方函数的栈底地址放在当前函数的栈底内存中,mov 是移值 ,lea是移地址, ebp指针从调用方main函数栈底放到被调用方sum函数栈底,系统给sum开辟栈帧,rep stos是循环拷贝,这里循环拷贝cccccccc,栈帧初始化。这也就是为什么我们写程序没有给局部变量初始化,打印出来为“烫烫烫”的原因。这里可以确定形参内存开辟在调用函数的栈帧上开辟。
那sum函数是如何返回值的呢?
在“tmp=a+b”下面可以看到首先是把[ebp+8]给eax,然后add(加等)[ebp+c],也就是10+20放到eax中,返回时用eax把返回值带出来。最后pop出栈,ebp又回到main函数栈底,这也就是中间保存0x100的原因(这里是假设栈底地址为0x100)。
sum函数执行完后,又怎么知道从那里接着执行?
ret 这里就是出栈下一行指令地址,然后放到pc寄存器,这样CPU就知道sum函数结束后从哪里接着执行。
这就是函数调用函数的过程。
接下来看一下函数的返回值:
内置类型:char short int long float double
结构体
union
enum
小于等于4个字节,通过eax寄存器带出;
大于4个字节小于等于8字节,通过eax和edx两个寄存器带出;
大于8字节,函数调用之前产生临时量内存,再把临时量地址入栈,被调用函数return处通过[ebp+8]访问临时量。
函数调用约定:
_cdecl c调用约定 调用方开辟形参内存,调用方释放形参内存
_stdcall Windows标准的调用约定 调用方开辟形参内存,被调用方自己释放形参内存
_fastcall 快速调用约定 调用方开辟形参内存,但是把最左边(也就是最后8字节的实参)通过 寄存器带入被调用函数中,被调用方自己释放
_thiscall C++的成员函数的调用约定
函数调用约定主要影响函数调用的三个方面:
1、函数产生的符号名字不同;
2、函数参数入栈顺序不同;
3、谁来清理形参的内存。