函数调用堆栈

1 函数的堆栈

#include<stdio.h>
int sum(int a, int b)
{	
	/*
	push		ebp
	mov			ebp,esp//将ebp移到栈顶(做新栈内存的栈底)
	sub			esp,0CCh
	push        ebx //空间初始化
	push        esi
	push        edi
	lea         edi,[ebp-0CCh]
	mov         ecx,33h
	mov         eax,0CCCCCCCCh
	rep stos    dword ptr es:[edi]
	*/
	int temp = 0;
	//mov       dword ptr[ebp-8], 0
	temp = a + b;
	//mov		eax,dword ptr[ebp+8]
	//add		eax,dword ptr[ebp+0Ch]
	//mov       dword ptr[ebp - 8], eax
	return temp;
	//mov		eax,dword ptr[ebp-8]
}

//mov		esp,ebp  释放空间
//pop		ebp		弹出ebp给栈底,即回归主调用	栈
//ret				弹出地址给PC指针,根据PC指针跳转
int main()
{
	//开辟栈空间
	/*
	push		ebp //将栈底ebp入栈
	mov			ebp,esp //将ebp移动到esp处
	sub			esp,0E4h //将栈顶esp向上移动 228字节(开辟228字节空间)
	push		ebx
	push		esi
	push		edi
	lea         edi,[ebp-0E4h]  
	mov         ecx,39h  //39次
	mov         eax,0CCCCCCCCh 
	rep stos    dword ptr es:[edi]  //初始化0CCCCCCCCh 

	*/
	int a = 10;
	//mov		dword ptr[ebp-8],0Ah
	int b = 20;
	//mov		dword ptr[ebp-14h],14h
	int ret = 0;
	//mov		dword ptr[ebp-20h],0
	ret = sum(a, b);
	//mov		eax,dword ptr[ebp-14h] //从右向左入栈
	//push		eax
	//mov		ecx,dword ptr[ebp-8]
	//push		ecx//被调动函数的形参参数是调动方开辟
	//	如果返回值类型大于8字节,即此时主调方会产生一个临时量的地址 压入push
	//call      sum(04D1023h) 将PC指针压入栈,跳转到sum函数入口

	//add		esp,8 回收形参内存
	//mov		dword ptr[ebp-20h],eax
	printf("ret=%d\n", ret);

	return 0;

}
/*

函数的调用时之前,需要扫描函数的声明和定义,然后对实参列表进行解参(与形参个数是否匹配,然后类型是否匹配(是否可以类型转换))

当在一个函数的运行期间调用另一个函数时,在运行被调函数之前,系统需要完成三件事:
1、将所有的实际参数,返回地址等信息传递给被调函数。
2、为被调函数的局部变量(也包括形参)分配存储空间
3、将控制转移到被调函数的入口
从被调函数返回主调函数之前,系统也要完成三件事:
1、保存被调函数的返回结果
2、释放被调函数所占的存储空间(esp=ebp)
3、依照被调函数保存的返回地址将控制转移到调用函数

所以被调函数的形参、返回地址、栈底ebp的空间其实是由主调方开辟的内存(在主调方栈里开辟内存),只是在被调方结束时自己释放内存而形参的内存却是由主调方释放
返回的结果如果大于8字节时,在主调方会产生临时量内存,被压入栈中。
在被调函数里对形参的访问是通过栈底操作 ebp+8 ebp+0ch

Call 两个动作
1.压入PC所指向地址
2.call跳转到对应函数(此时地址是偏移量)
Ret 两个动作
1.出栈,将栈顶元素赋给PC寄存器
2.根据PC跳转到call后面的指令

2 函数的返回值

临时量可能产生的阶段:
1.函数调用之前产生临时量。(接收返回值)
2.函数调用,在return 产生临时量
3.函数调用完成之后产生临时量 (const int &a)
内置类型返回的是常量,不可修改(右值,不可寻址),要想返回变量加可以被&返回的变量
自定义类型产生的临时量都是变量,可以修改
ps:返回的临时量都是放在一个寄存器里,只不过可能是数值,也可能是地址,需要进行两次拷贝。
(一次返回时将其拷贝给临时量,第二次将临时量的值拷贝给函数表达式)

3 函数调用约定

_cdecl C调用约定(默认)
_stdcall windows标准的调用约定
_fastcall 快速调用约定
_thiscall C++的成员函数的调用约定
_pascal (已弃用)

调用方式影响函数的三个方面

  1. 函数产生的符号名字不同
  2. 函数参数入栈顺序不同
    右->左
  3. 谁来清理形参的内存(主要看的还是谁来清理参数)
int _cdecl sum(int a,int b);  
//file1.cpp内声明  其.obj产生的符号sum在*UND*,对sum的引用
int _stdcall sum(int a,int b)  
{
return a+b;
}
//file2.cpp内定义     其.obj产生的符号sum在*.text*   

ps:
函数声明时会产生符号, 在UND区 —所有的符号引用都要到定位到对符号的定义(在链接时需要进行符号解析)
由于不同的调用方式产生的符号不同(编译器认为其就是不同的符号),无法进行符号解析

_cdecl 调用方开辟形参内存,调用方释放形参内存(主调方eps+8)
_stdcall 调用方开辟形参内存,被调用方自己释放形参内存(被调方ret 8 )
//ret 8
1.将地址弹出放入PC
2.系统自动回退8个字节地址(形参)
_fastcall 把最左边(最后入栈)(也就是最后8字节的实参通过寄存器带到被调用函数中)
若当参数大于8byte则需要入栈操作(调用方开辟形参内存),但也是把最左边(也就是最后8字节的实参通过寄存器带到被调用函数中),被调用方自己释放形参内存
PS其函数实参通过寄存器传给形参而不需要进行入栈,出栈

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值