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语言】你知道程序是如何调用函数的吗?

目录

1.函数栈帧的含义

概念?

要用到的汇编语言的知识

示例

2.理解栈帧

2.1 main函数栈帧的创建

2.2 局部变量的创建

2.3 函数传参

2.4 调用函数

2.5 函数返回?


? ? ? ? 一个.c文件在调用函数的时候(包括main 函数),其内存中的栈区有什么变化?要压栈、出栈哪些寄存器呢?函数的参数是如何进行传递的呢?函数调用结束之后栈区又是如何变化的呢?本文通过使用汇编语言,对这些内容进行了较为详细的剖析。

1.函数栈帧的含义

概念?

? ? ? ??首先,栈的概念想必不需要过多解释,那么什么是栈帧?引用百度百科:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从这句话中,可以提炼以下几点信息:

· 栈帧是一块因函数运行而临时开辟的空间。
· 每调用一次函数便会创建一个独立栈帧。
· 栈帧中存放的是函数中的必要信息,如局部变量、函数传参、返回值等。
· 当函数运行完毕栈帧将会销毁。

? ? ? ? 我们知道,C语言的内存区分成了 静态区、栈区、堆区,函数栈帧无疑是在栈区创建和销毁的,所以其要符合栈“后进先出”的特点。

要用到的汇编语言的知识

? ? ? ? 在这里使用汇编语言方面知识的原因是:通过它,我们可以深入底层了解一个程序是如何运行的,在何时——什么东西压栈,什么东西出栈,寄存器(汇编语言中一些用来暂时存储数据的东西)如何变化等等。这些都是C语言无法直观体现的,我们可以通过Visual Stdio 的在调试时的反汇编功能,将C语言代码转换成汇编语言代码,以便更好地观察。(另,C语言也是汇编语言编写的。)所以,简单地说,本文主要是在分析汇编语言的执行过程。

? ? ? ? 我们首先要了解几个汇编语言方面的东西,其中ESP和EBP时专门维护函数栈帧的,分别指向栈顶和栈底:

寄存器?用途
EAX累加寄存器:用于乘除法、函数返回值
EBX用于存放内存数据指针
ECX计数器
EDX用于乘除法、IO指针
ESP存放栈顶指针(其值是地址)
EBP存放栈底指针(其值是地址)

汇编指令用途
movmov A,B 将数据B移动到A
push压栈
pop出栈
call函数调用
add加法
sub减法
rep重复
lea加载有效地址

示例

? ? ? ? 比如,我们写下一个如下的C语言程序,非常容易,只有main() 函数和一个 Add() 函数,主函数里面调用了 Add() 。

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	int c = Add(a, b);
	return 0;
}

? ? ? ? 那么,一开始调用主函数的时候,主函数的函数栈帧就压栈;然后在主函数里面,调用了Add() 函数,此时Add() 的函数栈帧也要压栈。那么现在面临一个问题,是维护Add() 函数,还是维护main() 函数,亦或两者都维护?
? ? ? ? 其实,从平时使用Visual Stdio 调试的时候就可以看出来,当主函数内部调用一个函数A,按F11分步调试,会进入函数A的内部,函数A调用结束,会返回主函数。同理,实际上从进入函数A,一直到A 函数调用结束,这个过程都在维护函数A。所以,在main() 函数内部调用 Add() 函数之后,会出现如下图所示的情况,ebp和esp来维护Add() 函数的栈帧当Add() 函数调用结束,它的函数栈帧自然就会出栈,此时ebp、esp又会返回维护main() 函数的栈帧

2.理解栈帧

2.1 main函数栈帧的创建

? ? ? ? 实际上,main() 函数也是由其他函数调用的,其调用链条如下:

? ? ? ? 创建main函数的函数栈帧代码如下,汇编语言的注释是用 ; 所以这里 ; 后面的内容是注释,帮助理解代码。

006117A0  push        ebp                 ; ebp 压栈
006117A1  mov         ebp,esp             ; 将 esp里面的值 赋给ebp
006117A3  sub         esp,0E4h            ; esp减去0E4h(十六进制),得到的结果赋给esp
006117A9  push        ebx                 ; ebx 压栈
006117AA  push        esi                 ; esi 压栈
006117AB  push        edi                 ; edi 压栈
006117AC  mov         edi,[ebp-24h]       ; 将ebp往上数,第24h(16进制)个字节开始,向下4个字节的值赋给edi
006117AF  mov         ecx,9               ; 这里到结束的意思是:
006117B4  mov         eax,0CCCCCCCCh      ; 将0CCCCCCCCh 赋值给某块空间,这块空间从附加段中 edi 指向的位置开始
006117B9  rep stos    dword ptr es:[edi]  ; 一共执行九次(0CCCCCCCCh 是四个字节的内容,每次操作四个字节,所以一共操作了36字节)

第一行?

? ? ? ? 我们来开始逐句剖析上方代码,首先执行第一行(图中红色圆圈圈出来的黄色箭头,表示已经执行完其上一行,按F10调试就执行当前行),由于是压栈操作,所以esp的值会有所变化,如下图右边监视窗口,esp的值(十六进制显示的)相较之前改变了,所以变成红色:

? ? ? ? 如下,ebp压栈,同时esp上移:

第二行

? ? ? ? ?该行是将esp的值赋给ebp,效果也如下图,右边监视窗口的红色部分所示。

? ? ? ? 如下,将esp的值赋给ebp之后,ebp和esp指向同一块地方:
?

第三行

? ? ? ? 该行是将esp的值减去0E4h(十六进制),得到的结果赋给esp,如下图。

? ? ? ? ?如图所示,由于图中从下往上是地址高处到地址低处,所以esp值变小,实际上图中是上移。并且,现在ebp和esp维护的空间,就是main函数的函数栈帧:

第四行

? ? ? ? 压栈,压入ebx,改变栈顶指针esp的值。

? ? ? ? 如下,压栈,esp上移:

第五行

? ? ? ? 压入esi,改变esp的值。

? ? ? ? 如下,和上一步类似:
?

第六行

? ? ? ? 压入edi,改变esp的值。?

? ? ? ? 和上一步也类似:

第七行

? ? ? ? 将[ebp-24h] 表示的地址赋值给edi。

? ? ? ? 这里就是把 edi 里面的值改变,从函数栈帧看不出什么,看上面的监视图就可以直到确实是改变了。

最后三行

? ? ? ? 如之前代码里的注释所说。

? ? ? ? 如下,两个箭头指示的值是一样的,其代表的是edi所表示的地址,从该地址开始,往后9个dw(double word 双字,一个双字等于四个字节)的内容,都赋值为cccccccc (十六进制)。

? ? ? ? 这三行代码效果如下:

? ? ? ? 整个过程可以用一张动图生动形象地展示:

2.2 局部变量的创建

? ? ? ? 接下来,我们在汇编代码中鼠标右击,然后将下图红色箭头所指示的"显示符号名" 的勾去掉。

? ? ? ? 发生改变的是下图中红色圆圈圈出来的,可以看出,原本所有的变量名,都变成了寄存器减去某个十六进制数字。他们实际上是等价的,即变量的地址就等于替换后的地址

? ? ? ? 接下来分析局部变量创建过程。
? ? ? ? 首先,创建变量a,代码如下,其含义就是,将0Ah 这个十六进制数字,从ebp的地址低八位处开始放,占四个字节

002C17C5  mov         dword ptr [ebp-8],0Ah  

? ? ? ? 如下图,可以通过两个红色箭头看到,右边监视的ebp的值就是左边 地址处的箭头指向的地址,说明这就是ebp的地址,然后减去八位,再根据栈从下往上使用以及Visual Stdio小端存储的特点,就成了内存区里面红色方框框出来的内容。(注意,比如 cc cc cc cc cc占据的是一个字节的空间,四个cc 就占据四个字节,而汇编语言中,地址-1,只跳过一个c,所以ebp-8是跳过8个c,即四个字节)

? ? ? ? 如下,图中一个小格子代表四个字节,不难看出,变量a存储的位置,在栈底指针往上跳过四个字节的地方。

? ? ? ? 然后创建局部变量b,通过内存图可以看出,变量b和变量a是间隔八个字节的:

? ? ? ? 如下图:

2.3 函数传参

? ? ? ? 代码如下:

002C17D3  mov         eax,dword ptr [ebp-14h]   ; 将变量b的值赋给eax
002C17D6  push        eax                       ; eax压栈
002C17D7  mov         ecx,dword ptr [ebp-8]     ; 将变量a的值赋给ecx
002C17DA  push        ecx                       ; ecx压栈

? ? ? ? 执行前两行代码,确实将变量b的值赋给了eax,然后eax引起的压栈导致了esp改变:

? ? ? ? 如下图,不要忘了eax里面的值和变量b是一样的哦:

? ? ? ? 执行后两行代码:

? ? ? ? 其效果和前两行类似,同时ecx里面存的是变量a的值:

2.4 调用函数

? ? ? ? 上面的内容执行完之后,要执行如下语句,其意思是,执行?002C10B4 地址处的内容:

002C17DB  call        002C10B4  

? ? ? ? 然后我们将其滑倒该地,发现是这样的,意思是跳到002C1740地址处:

? ? ? ? ?又找到该地址,发现如下,所以,通过这两步调用Add() 函数,如下红色部分,和创建main函数的函数栈帧类似,实际上就是创建了Add() 的函数栈帧

? ? ? ? 效果如下,建立了Add函数的函数栈帧:

? ? ? ? 红色个方框后面两行代码不是很重要,是用来检查bug的,如下代码和图片:

002C1757  mov         ecx,2CC003h  
002C175C  call        002C130C  

?

2.5 函数返回?

? ? ? ? 函数返回

002C1761  mov         eax,dword ptr [ebp+8]   
002C1764  add         eax,dword ptr [ebp+0Ch]  

? ? ? ? 第一行代码:?将ebp+8 地址处的数据放到eax?。

? ? ? ? 第二行代码:将ebp+0Ch 地址处的数据和eax相加,结果存到eax里面。

? ? ? ? 如下图中,由于图片从下往上是地址从高到低,所以图片中ebp+8是在ebp下方。实际上就是ecx和eax的值相加,然后存到eax里面。eax里面存储变量b的值,ecx里面存储变量a的值,最后eax的值就是变量a、b之和。并且eax是不会随着Add() 函数的函数栈帧销毁而改变值。

? ? ? ? 通过监视也可以看出,eax的值变成0x0000001e,转换成十进制就是30。
?
? ? ? ? 此时已经拿到返回值,存储在eax里面,还要执行以下几行代码:

00AA13F1  pop         edi  
00AA13F2  pop         esi  
00AA13F3  pop         ebx  
00AA13F4  mov         esp,ebp  
00AA13F6  pop         ebp  
00AA13F7  ret  

? ? ? ? ?就是出栈、赋值等等,结果如下,回到了调用Add() 函数之前的状态:

? ? ? ? 然后执行main() 函数后续代码代码,如下图红色框出:

? ? ? ? 第一行:esp加8,即esp在途中向下移动四个字节。

? ? ? ? 第二行,将eax的值赋给ebp-20h 地址处。

? ? ? ? 执行完之后,调试图如下,通过对比两个红色方框的内容,左边红色方框的地址,和右边&c 的值一样,说明那就是变量c 存储的地方,其值也是变量c 的值:

? ? ? ? 示意图如下:

? ? ? ? 通过对函数栈帧创建、销毁过程的剖析使我们不仅了解计算机做了什么,还了解了它是如何做的。通过函数栈帧尝试解析递归等问题相信也会更加直观。由于本人水平有限,不足之处还请大家多多指教。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:47:31  更:2022-12-25 10:50:42 
 
开发: 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年11日历 -2024/11/22 19:10:28-

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