C与C++的区别之函数调用堆栈

函数调用栈

1、函数参数带入(入调用方函数的栈,从右向左入栈)
int fun(int a);
int fun(int a, int b);
int fun(int a, int b, int c);
形参字节							参数带入方式	
byte<=4							push a 入栈
byte>=4 && byte<=8 				push b push a 入栈
byte>=12						先让栈顶偏移12字节(参数大小)	sub esp,0ch
		再将实参的值写入的偏移的内存中,从栈顶方向向栈底方向写入 c、b、a 如果是数组入栈,则对应顺序应是a[0],a[1],a[2]
2、函数栈帧开辟
int fun(int a, int b)//主函数中调用该函数
{
	int c = a + b;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int rt = 0;
	rt = fun(a, b);
	return 0;
}
	1.实参入栈
	2.将main(调用方)栈底地址入栈
	3.让 ebp=esp			//ebp(栈底指针)指向esp(栈顶指针)位置,充当新函数(调用的函数fun())的栈底
	4.指令:"sub esp,栈帧大小"	//向低地址(因为栈是从高地址向低地址生长的)开辟空间,esp栈顶指针向低地址偏移
	5.将栈帧内存初始化为0xcccccccc		//这就是为初始化变量显示"燙燙燙"的原因,部分编译器会初始化成随机值

在这里插入图片描述
在这里插入图片描述

3、函数返回值的带出
	4字节					eax寄存器
	4<=8字节的返回值带出	两个寄存器,eax,edx
	>8字节返回值带出		
			在main(调用方)的栈帧上预留一部分空间
			将返回值写入到main预留的空间内
			将该部分的地址写入到eax寄存器,由eax寄存器带出
			使用时返回值时,从eax寄存器中存储的地址找到存储的位置取出返回值
4、函数栈帧的回退
	将多个寄存器pop			//pop edi esi ebx
	esp=edp					//回收fun函数的栈帧,esp指向当前栈底 
	pop main栈底地址			//edp=pop    edp回退至调用前的状态(调用方的栈底)
	ret						//返回调用方
	esp+8					//清栈,清理开辟的形参空间,esp所加大小视形参所占空间而定

若返回值字节数小于4,则只需一个寄存器eax即可带出

007B1733  mov         eax,dword ptr [c]  

若返回值字节数大于4小于8,则需要两个寄存器eax、edx带出

007B1733  mov         eax,dword ptr [c]  
007B1736  mov         edx,dword ptr [ebp-2Ch]  

若返回值字节数大于8,则需要提前开辟内存空间,将返回值存放与此空间中,由eax带出该空间的地址

struct test	//测试大于8个字节的返回值
{
	int a;
	int b;
	int c;
};
001441C8  mov         eax,dword ptr [ebp+8]  
001441CB  mov         ecx,dword ptr [test]  
001441CE  mov         dword ptr [eax],ecx  
001441D0  mov         edx,dword ptr [ebp-40h]  
001441D3  mov         dword ptr [eax+4],edx  
001441D6  mov         ecx,dword ptr [ebp-3Ch]  
001441D9  mov         dword ptr [eax+8],ecx  
001441DC  mov         eax,dword ptr [ebp+8] 
函数调用过程分析与汇编指令解析

int fun(int a, int b)
{
	/*
	push ebp		//保存旧栈底
	mov	 ebp,esp	//ebp = esp		//ebp从旧栈底(mian)跑到新栈底(fun)
	sub  esp,0cch	//开辟栈帧
	push ……		//push寄存器	//pop  ebx esi edi 
*	lea  edi,[ebp-0CCh]				//edi保存未把寄存器入栈前的栈顶
*	mov  ecx,33h
	mov  0xcccccccc	//初始化前    初始化范围为寄存器与栈底之间的内容
*	rep stos dword ptr es:[edi]   //循环初始化内存为0xcccc cccc
	*/
	int c = a + b;	// eax,dword ptr [a] \ eax,dword ptr [b] \ dword ptr [c],eax
	return c;
	/*
	mov  eax,dword, ptr[c]
	*/
}
/*
pop  ……		//pop 多个寄存器
mov  esp,ebp	//回收栈,将esp指向(fun)栈底
pop  ebp		//ebp=pop	//ebp回到原栈底
ret				//近返回的指令,将栈顶字单元保存的偏移地址作为下一条指令的偏移地址
*/

int main()
{
	int c = fun(10, 20);
	/*
	push 14h
	push 0ah
	call fun(xxxxxxx)			//压如下一行指令 \ 跳转到fun()

	add esp,8					//清栈
	mov dword ptr[c],eax
	*/

	return 0;
}
5、三种调用约定的比较

函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。常见的的函数调用约定有以下几种:_cdecl (c标准调用约定)、_stdcall(windows下标准调用约定) 、_fastcall(快速调用约定) …

					入参顺序		入参方式(size)			返回值带出				参数清除
	__cdecl 		从右向左		<=8 push 				<=8 寄存器				调用方(main)	->  add esp,4
								>8 提前开辟内存			>8 提前开辟内存
								
	__stdcall		从右向左		<=8 push 				<=8 寄存器				被调用方(fun) ->  ret 8	//返回到原函数且清除形参(pop + add)
								>8 提前开辟内存			>8 提前开辟内存
								
	__fastcall		从右向左		<=8 直接寄存器带入		<=8 寄存器				被调用方(fun),没有入栈,栈帧回退直接清除  
								>8 超过部分开辟内存		>8 提前开辟内存

函数符号生成规则:

  • _cdecl调用约定:
    “?”+函数名+ “@@YA” + 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。如:
    int __cdecl bom(void) -> (?bom@@YAHXZ)
    int __cdecl Sum(int,int) -> (? Sum@@YAHHH@Z)

  • _stdcall调用约定:
    “?”+ 函数名 + “@@YG” + 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。如:
    int __stdcall bom(void) -> (? bom@@YGHXZ)
    int __stdcall Sum(int,int) -> (? Sum@@YGHHH@Z)

  • _fastcal调用约定:
    “?”+ 函数名 + “@@YI”+ 返回值类型 + 参数类型 + “@Z”或“Z”(无参数)。
    int __fastcall bom(void) -> (? bom@@YIHXZ)
    int __fastcall Sum(int,int) -> (? Sum@@YIHHH@Z)

参数类型代号表
XDEFHIJKMN_NUPA+PB+
voidcharunsigned charshortintunsigned intlongunsigned longfloatdoubleboolstruct指针+其类型const指针+其类型

三种调用约定实例:

  1. __cdecl 调用约定
int __cdecl Sum(int a, int b);	// 函数符号 (?Sum@@YAHHH@Z)

int main()
{
	int ret = Sum(10,20);
	/* 反汇编
	004F1848  push        14h  					// 将形参20入栈
	004F184A  push        0Ah  					// 将形参10入栈
	004F184C  call        Sum (04F115Eh)  		// 调用Sum()函数
	004F1851  add         esp,8  				// 栈顶指针回退8字节 ————清理形参
	004F1854  mov         dword ptr [ret],eax  	// 将eax内的数据(返回值),赋值到ret中,赋值double word(4字节)大小
	*/
	return 0;
}
		
int __cdecl Sum(int a, int b)
{
	return a + b;
	/* 反汇编
	004F1738  mov         eax,dword ptr [a]  
	004F173B  add         eax,dword ptr [b]  	// 将a+b的结果保存在eax寄存器中
	*/
}
/* 反汇编
... 
004F174E  mov         esp,ebp  		// ...
004F1750  pop         ebp  			// 栈帧回退
004F1751  ret  						// 回调至调用方(main)
*/
  1. __stdcall 调用约定
int __stdcall Sum(int a, int b);

int main()
{
	int ret = Sum(10,20);
	/*
	00861848  push        14h    				// 将形参20入栈
	0086184A  push        0Ah   				// 将形参10入栈
	0086184C  call        Sum (08611B3h)  		// ..
	00861851  mov         dword ptr [ret],eax   // ..
	*/
	return 0;

}
		
int __stdcall Sum(int a, int b)
{
	return a + b;
	/*
	00861738  mov         eax,dword ptr [a]  
	0086173B  add         eax,dword ptr [b]  	// ..
	*/
}
/*
... 
0086174E  mov         esp,ebp  		// ..
00861750  pop         ebp  			// ..
00861751  ret         8				//①ret 弹出偏移地址..  ②esp+8 回收栈空间(清理形参)
*/  
  1. __fastcall 调用约定
int __fastcall Sum(int a, int b);

int main()
{
	int ret = Sum(10,20);
	/*
	009C1848  mov         edx,14h  					// edx 带入参数20
	009C184D  mov         ecx,0Ah  					// ecx 带入参数10
	009C1852  call        Sum (09C1384h)  			// ..
	009C1857  mov         dword ptr [ret],eax 		// ..
	*/
	return 0;
}
		
int __fastcall Sum(int a, int b)
{
	return a + b;
	/*
	009C1740  mov         eax,dword ptr [a]  
	009C1743  add         eax,dword ptr [b]  		// ..
	*/
}
/*
...  
009C1756  mov         esp,ebp  		// ..
009C1758  pop         ebp  			// ..
009C1759  ret  						// ..
*/

测试环境为Microsoft Visual Studio 2019

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值