• 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] 
    
    5、三种调用约定的比较

    函数调用约定,是指当一个函数被调用时,函数的参数会被传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。基本的函数调用约定由以下几种:

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

    测试环境为Microsoft Visual Studio 2019

  • 相关阅读:
    python中的__init__方法
    hosts文件是做什么的
    自动化测试--环境搭建python+pytest+selenium
    什么是Netty服务器
    Java 基础原理一
    Python MongoDB 基本操作
    Mysql 数据库的查询操作
    Git 笔记二
    Git 使用笔记
    最全正则表达式
  • 原文地址:https://www.cnblogs.com/TaoR320/p/12680161.html
Copyright © 2020-2023  润新知