函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)

简介: 函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)

5.函数传参的准备

1.创建形参

下面两条指令在main函数的栈帧中创建了x和y这两个局部变量

mov   dword ptr [ebp-8],0Ah
mov   dword ptr [ebp-14h],0Bh
把0Ah(就是10进制的10)赋值给ebp-8内存空间的值
把0Bh(就是10进制的11)赋值给ebp-14h内存空间的值

这里我们就可以回答第一个问题了:局部变量是如何创建的?

局部变量是通过栈底指针的偏移定位到空间

将对应空间分配给局部变量

然后将该空间内的值修改为对应初始化的值

下面四条指令是:

mov     eax,dword ptr [ebp-14h]
push    eax
mov     ecx,dword ptr [ebp-8]
push    ecx
本质是形成形参进行函数传参
其中这个eax中放的是y的值
ecx中放的是x的值

因此这也就解释了第三条问题:

函数是如何传参的?

通过栈底指针的偏移找到实参的值,

把对应实参的值传递给寄存器eax和ecx

然后把eax和ecx的值入栈

就形成了形参

也就是说形参是实参的一份拷贝,改变形参不会影响实参

传参的顺序是怎样的?

顺序是从右向左传参

eax变为11

ecx变为10

2.将call指令的下一条指令的地址压栈

下面就要进行call指令调用MyAdd函数了

首先编译器会先把call指令的下一条指令保存下来

也就是说这里实际上是把add指令的地址压入了栈帧当中

也就是执行了

push add的地址

下一条是jmp命令

这个命令就很简单

执行完jmp命令后就正式进入了MyAdd函数

jmp  00bb13c0
• 1

这样我们就能回答第5个问题了:

函数调用是怎么做的?

函数调用时,先形成形参,

然后把main函数的栈底指针的地址保存下来

然后把对应函数调用指令的下一条指令的地址压入栈中

然后正式进入MyAdd函数

6.正式进入MyAdd函数并且开辟栈帧,置为随机值

然后我们进入MyAdd函数

下面这10条指令跟创建main函数栈帧的指令如出一辙

就是先把main函数的栈底地址入栈

然后把esp的值赋给ebp:其实就是正式进入MyAdd这个函数的栈帧

然后sub esp,0CCh就是为MyAdd函数创建栈帧

push ebx,esi,edi 为后续初始化MyAdd函数栈帧做准备

rep stos dword ptr es:[edi]: 就是把MyAdd函数栈帧中的空间初始化为随机值0CCCCCCCCh

push        ebp  
mov         ebp,esp  
sub         esp,0CCh  
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp+FFFFFF34h]  
mov         ecx,33h  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi] 

7.取出形参的值

然后下面这三条指令就是:

mov         eax,dword ptr [ebp+8]  
add         eax,dword ptr [ebp+0Ch]  
mov         dword ptr [ebp-8],eax  
其实就是通过ebp栈底指针的偏移找到创建的形参,取出形参的值
赋值,相加给eax
然后通过eax的赋值和ebp栈底指针的偏移创建MyAdd函数栈帧中的局部变量c

8.销毁MyAdd函数栈帧

下面这5条指令就是

mov eax,dword ptr [ebp-8]
pop edi
pop dsi
pop ebx
mov esp,ebp

保存返回值并且销毁MyAdd这个函数栈帧

9.返回

其中:

返回的本质是:

1.返回到main函数的栈帧

2.返回到call指令的下一条指令的地址处

3.如果有返回值,把返回值进行返回

下面两条指令就会

分别将ebp和esp返回到main函数中的对应的位置

pop     ebp
ret

最后两条指令

add esp,8
mov    dword ptr [ebp-20h] eax

这样就能回答第6个问题了

函数调用结束之后是怎么返回的呢?

首先先把返回值进行临时拷贝,在MyAdd这个函数中是用寄存器eax来保存这个返回值

然后通过弹出main函数的栈底地址,将栈底指针ebp恢复为main函数的栈底指针

通过弹出call指令的下一条指令的地址并将这个地址存入eip中

保证call指令结束后能够回到call指令的下一条指令的位置

我们同时也可以看出:

形参先创建,然后再调用函数

函数先销毁,然后再销毁形参

函数的销毁和形参的销毁都是通过栈顶指针的增大实现的

函数栈帧的创建是通过栈顶指针的减小实现的

在函数中创建局部变量是通过栈底指针的偏移定位到要创建的位置

通过eax,ecx等等寄存器来完成变量值的传递

以上就是以MyAdd为例,

整个函数栈帧的创建和销毁过程的分析

四.总结

综上,我们可以得出

以上就是函数栈帧的创建和销毁的全部内容,希望能对大家有所帮助!

相关文章
|
1月前
|
存储 安全 数据管理
C语言之考勤模拟系统平台(千行代码)
C语言之考勤模拟系统平台(千行代码)
54 4
|
24天前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
1月前
|
存储 安全 物联网
C语言物联网开发之设备安全与代码可靠性隐患
物联网设备的C语言代码安全与可靠性至关重要。一是防范代码安全漏洞,包括缓冲区溢出和代码注入风险,通过使用安全函数和严格输入验证来预防。二是提高代码跨平台兼容性,利用`stdint.h`定义统一的数据类型,并通过硬件接口抽象与适配减少平台间的差异,确保程序稳定运行。
|
24天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
56 1
|
2月前
|
存储 搜索推荐 C语言
深入C语言指针,使代码更加灵活(二)
深入C语言指针,使代码更加灵活(二)
|
2月前
|
存储 程序员 编译器
深入C语言指针,使代码更加灵活(一)
深入C语言指针,使代码更加灵活(一)
|
2月前
|
C语言
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
深入C语言指针,使代码更加灵活(三)
|
22天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
44 10
|
22天前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
43 9
|
22天前
|
存储 Unix Serverless
【C语言】常用函数汇总表
本文总结了C语言中常用的函数,涵盖输入/输出、字符串操作、内存管理、数学运算、时间处理、文件操作及布尔类型等多个方面。每类函数均以表格形式列出其功能和使用示例,便于快速查阅和学习。通过综合示例代码,展示了这些函数的实际应用,帮助读者更好地理解和掌握C语言的基本功能和标准库函数的使用方法。感谢阅读,希望对你有所帮助!
32 8