IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言】函数栈帧的相关知识,希望可以帮助你 -> 正文阅读

[C++知识库]【C语言】函数栈帧的相关知识,希望可以帮助你

引言 |??函数栈帧实质是指在内存栈区为函数所开辟的一块空间,使函数得以运行,本章简要介绍函数栈帧的创建及销毁,涉及少量的汇编代码。? ? ? ? VS2013演示

1.寄存器

?寄存器是集成在中央处理器上的元件,可以临时存储数据(就像内存一样,但其访问速度较内存快),例如eax? ebx? ecx? edx? ebp? esp? ,其中ebp(栈底指针),esp(栈顶指针)存放的是地址,这两的存放的地址用于维护函数指针;

ebp(栈底指针)? esp(栈顶指针)

压栈(push):向栈顶放置一个元素; 出栈(pop):从栈空间拿走一个元素

2.main()主函数

在实际程序运行中,main()函数也是被其他的函数调用,在写好实例代码后:

#include<stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 13;
	int b = 12;
	int c = 0;

	c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

?按下F10启动逐过程调试,调试->窗口->调用堆栈,

(这里看到main()函数被调用)调试至结束后(return 0;语句走完);

由于栈空间是从高地址向低地址的次序使用的,故mainCRTStartup()函数首先调用__tmainCRTStartup()函数,之后__tmainCRTStartup()函数调用main()函数,可以在源代码ctrexe.c中找到相关函数名及调用方式;

?3.main()函数及Add()函数运行是对应的汇编代码;

在shiift+F5退出后,再按F10进入调试,调试->窗口->反汇编,找到对应的汇编代码;

3.1. main()主函数的对应汇编代码

--- d:\learningfile\class103\test\函数栈帧\函数栈帧\test.c ----
int main()
{
01011410  push        ebp  
01011411  mov         ebp,esp  
01011413  sub         esp,0E4h  
01011419  push        ebx  
0101141A  push        esi  
0101141B  push        edi  
0101141C  lea         edi,[ebp-0E4h]  
01011422  mov         ecx,39h  
01011427  mov         eax,0CCCCCCCCh  
0101142C  rep stos    dword ptr es:[edi]  
	int a = 13;
0101142E  mov         dword ptr [ebp-8],0Dh  
	int b = 12;
01011435  mov         dword ptr [ebp-14h],0Ch  
	int c = 0;
0101143C  mov         dword ptr [ebp-20h],0  

	c = Add(a, b);
01011443  mov         eax,dword ptr [ebp-14h]  
01011446  push        eax  
01011447  mov         ecx,dword ptr [ebp-8]  
0101144A  push        ecx  
0101144B  call        010110E1  
01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax  

	printf("%d\n", c);
01011456  mov         esi,esp  
01011458  mov         eax,dword ptr [ebp-20h]  
0101145B  push        eax  
0101145C  push        1015858h  
01011461  call        dword ptr ds:[01019114h]  
01011467  add         esp,8  
0101146A  cmp         esi,esp  
0101146C  call        0101113B  

	return 0;
01011471  xor         eax,eax  
}
01011473  pop         edi  
}
01011474  pop         esi  
01011475  pop         ebx  
01011476  add         esp,0E4h  
0101147C  cmp         ebp,esp  
0101147E  call        0101113B  
01011483  mov         esp,ebp  
01011485  pop         ebp  
01011486  ret  

?3.2. Add()函数对应的汇编代码:

--- d:\learningfile\class103\test\函数栈帧\函数栈帧\test.c -------
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int Add(int x, int y)
{
010113C0  push        ebp  
010113C1  mov         ebp,esp  
010113C3  sub         esp,0CCh  
010113C9  push        ebx  
010113CA  push        esi  
010113CB  push        edi  
010113CC  lea         edi,[ebpebp-0CCh]  
010113D2  mov         ecx,33h  
010113D7  mov         eax,0CCCCCCCCh  
010113DC  rep stos    dword ptr es:[edi]  
	int z = 0;
010113DE  mov         dword ptr [ebp-8],0  
	z = x + y;
010113E5  mov         eax,dword ptr [ebp+8]  
010113E8  add         eax,dword ptr [ebp+0Ch]  
010113EB  mov         dword ptr [ebp-8],eax  
	return z;
010113EE  mov         eax,dword ptr [ebp-8]  
}
010113F1  pop         edi  
}
010113F2  pop         esi  
010113F3  pop         ebx  
010113F4  mov         esp,ebp  
010113F6  pop         ebp  
010113F7  ret  

main()汇编代码的理解:在执行main函数前,__tmainCRTStartup()函数的栈帧已存在,其中epb指向该函数栈帧的底部(高地址处),esp指向该函数栈帧的顶部;

 ——————————————————   <<<----esp
|                  |                 |  低地址
|                  |                 |      
|                  |                 |
|  __tCRTStartuo() |                 |
|    的函数栈帧     |                 |
|                  |                 |
|                  |                 |
 __________________   <<<------ebp   |  高地址            

?main()函数栈帧预开辟:

01011410  push        ebp  
01011411  mov         ebp,esp  
01011413  sub         esp,0E4h 

push指令(压栈)表示将ebp的值压入栈中,产生新的栈顶,同时esp存放地址会减小,保持其存放地址始终是栈顶,mov指令表示将esp的值赋值给ebp,时ebp指向的地址发生改变,sub指令表示减法,将esp减去0E4h(h表示该数字为十六进制)后更新esp,此时ebp与esp之间的空间即为main()函数的函数栈帧,如图:


 ——————————————————     <<<----esp       
|                  |                  |    低地址
|                  |                  |
|    main()函数     |                 |
|    的函数栈帧     |                  |
|                  |                  |
|                  |                  |
|                  |                  |
 ——————————————————     <<<------ebp  |
|        ebp       |                  |
 ——————————————————                   |
|                  |                  |  
|                  |                  |      
|                  |                  |
|  __tCRTStartuo() |                  |
|    的函数栈帧     |                  |
|                  |                  |
|                  |                  |
 __________________                   |  高地址 
01011419  push        ebx  
0101141A  push        esi  
0101141B  push        edi 

三个push指令表示将这三个值压入栈中,同时esp会发生相应减小:



 ——————————————————     <<<----esp    |      低地址
|      edi         |                  |
 ——————————————————                   |
|      esi         |                  |
 ——————————————————                   |
|      ebx         |                  |
 ——————————————————                   |
|                  |                  |    
|                  |                  |
|    main()函数     |                 |
|    的函数栈帧     |                  |
|                  |                  |
|                  |                  |
|                  |                  |
 ——————————————————     <<<------ebp  |      高地址
0101141C  lea         edi,[ebp-0E4h]  
01011422  mov         ecx,39h  
01011427  mov         eax,0CCCCCCCCh  
0101142C  rep stos    dword ptr es:[edi]

lea指令(load effective address加载有效地址)表示将ebp中存放的地址值减去0E4h后存放在edi中;mov指令表示分别将39h与0cccccccch放入ecx与eax中;rep stos指令表示将以edi存放的地址开始向下的ecx次个dword的字节(word表示两个字节,dword即double word,表示4字节)修改为eax的内容,由于0E4h = ecx * dword ,故main的函数栈帧被全部更改为cc;

	int a = 13;
0101142E  mov         dword ptr [ebp-8],0Dh 

mov指令表示将0Dh的值(对应的十进制是13)放入epb-8字节的地址处,此时int占据4个字节,使用完后离ebp所指向的真实地址还有4个空间,这样设计是为了防止越界访问造成的影响:

?

 ——————————————————
|      edi         |
——————————————————                   |     低地址
|      esi         |                  |
 ——————————————————                   |
|      ebx         |                  |
 ——————————————————                   |
|                  |                  |    
|                  |                  |
|    main()函数     |                 |
|    的函数栈帧     |                  |
|                  |                  |
 ——————————————————   < epb -8        |
|     a = 13      |                   |
 ——————————————————  < ebp -4         |
|     四个字节     |                   |
 ——————————————————     <<<------ebp  |      高地址

这就是为什么在局部变量未赋值时为“烫烫”的原因,里面全部都是cc;接下来的赋值指令都是如此执行的,变量之间相差了8个字节 ;

	c = Add(a, b);
01011443  mov         eax,dword ptr [ebp-14h]  
01011446  push        eax  
01011447  mov         ecx,dword ptr [ebp-8]  
0101144A  push        ecx  
0101144B  call        010110E1  
01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax 

mov指令表示将ebp-14h处4字节的空间中的值放入eax寄存器中,push指令表示将eax压入栈区,(这里即是创建形参,注意是从右向左传参,先将最右的值压栈,逐渐向右压栈)esp存放地址改变;在执行call指令前,按下F11(逐语句调试)会出现:

_Add:
00FC10E1  jmp         00FC13C0 

call指令表示执行之后的地址010110E1处的指令,以及将call指令下一条指令的地址(01011450)压入栈中,jmp指令表示跳至00FC13C0地址处,即开始执行Add函数汇编代码,经过预开辟空间,赋值:

int Add(int x, int y)
{
010113C0  push        ebp  
010113C1  mov         ebp,esp  
010113C3  sub         esp,0CCh  
010113C9  push        ebx  
010113CA  push        esi  
010113CB  push        edi  

?对应图示:

 


 ——————————————————  <<<----esp(新)  |      低地址
|      edi         |                  |
 ——————————————————                   |
|      esi         |                  |
 ——————————————————                   |
|      ebx         |                  |
——————————————————    <<<----esp(旧) |
|                  |                  |
|                  |                  |
| add()函数预开辟   |                  |
|   空间           |                  |
|                  |                  |
|                  |                  |
|                  |                  |
|                  |                  |
 ——————————————————   <<<------ebp    |    更低的地址
|   epb(存放的是    |                  |
|  main的 栈底地址) |                  |
 ——————————————————                   |
|    01011450      |                  |
 ——————————————————   <<<---ebp + 8   |
|   ecx  a = 13    |                  |
 ——————————————————   <<---ebp + 0Ch  |
|   eax b = 12     |                  |
 ——————————————————                   |     低地址
|      esi         |                  |
 ——————————————————                   |
|      ebx         |                  |
 ——————————————————                   |  ————————————————————————
|                  |                  |    
|                  |                  |
|    main()函数    |                  |
|    的函数栈帧     |                  |           均为main()函
|                  |                  |         数的函数栈帧
 ——————————————————                   |
|     a = 13      |                   |
 ——————————————————                   |
|     四个字节     |                   |
 ——————————————————                   |      高地址 ——————————————

下一部分:?

	int z = 0;
010113DE  mov         dword ptr [ebp-8],0  
	z = x + y;
010113E5  mov         eax,dword ptr [ebp+8]  
010113E8  add         eax,dword ptr [ebp+0Ch]  
010113EB  mov         dword ptr [ebp-8],eax  
	return z;
010113EE  mov         eax,dword ptr [ebp-8]  
}

?010113E5代码与010113E8代码通过ebp加整数方式找到ecx将里面的值赋给eax,在通过此方法将eax的值找到加给eax(更新eax的值),010113EB代码将eax的值放入[epb - 8]的四个字节的空间(即z的空间)中,010113EE代码将[epb - 8]的四个字节的空间的值重新放入eax寄存器,防止Add函数空间释放后无法找到所需要的值

	return z;
00FC13EE  mov         eax,dword ptr [ebp-8]  
}
00FC13F1  pop         edi  
00FC13F2  pop         esi  
00FC13F3  pop         ebx  
00FC13F4  mov         esp,ebp  
00FC13F6  pop         ebp  
00FC13F7  ret 

?三次将寄存器弹出后,esp被mov指令指向ebp所指向空间,在pop指令将epb弹出后,esp指向更新,同时ebp重新指向main()函数栈底;ret是执行流程导向?01011450 指令,此时执行流程回到主函数(读完01011450 地址后,esp自动更新):



 ——————————————————                   |
|    01011450      |                  |
 ——————————————————    <<<------esp   |
|   ecx  a = 13    |                  |
 ——————————————————                   |
|   eax b = 12     |                  |
 ——————————————————   <<<----esp+8    |     低地址
|      edi         |
 ——————————————————
|      esi         |                  |
 ——————————————————                   |
|      ebx         |                  |
 ——————————————————                   |  ————————————————————————
|                  |                  |    
|                  |                  |
|    main()函数    |                  |
|    的函数栈帧     |                  |           均为main()函
|                  |                  |         数的函数栈帧
 ——————————————————                   |
|     a = 13      |                   |
 ——————————————————                   |
|     四个字节     |                   |
 ——————————————————    <<<---ebp      |      高地址 ——————————————
01011450  add         esp,8  
01011453  mov         dword ptr [ebp-20h],eax  

add指令?表示esp重新指向esp+8地址,ecx与eax两个空间;mov指令表示将exa里的值放入[ebp-20h]所指向的4字节空间,即变量c的空间,函数变量传递完成。

结语? |? 局部变量的创建、函数传参及其顺序、形参与实参的关系,函数如何调用以及如何返回。

注:不同编译器实现略有差异,但基本原理相同

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 08:54:26  更:2021-08-29 08:56:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 20:42:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计