深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-1

什么是函数栈帧❓

    我们在写C 语言代码的时候,经常会把一个独立的功能抽象为函数,所以 C程序是以函数为基本

单位的。那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问

题都和函数栈帧有关系。

     

       函数栈帧(stack frame) 就是函数调用过程中在程序的调用栈(call stack)所开辟的空间, 这些空间是用来存放:

函数参数和函数返回值
临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
保存上下文信息(包括在函数调用前后需要保持不变的寄存器)

理解函数栈帧能解决什么问题呢?

理解函数栈帧有什么用呢?

只要理解了函数栈帧的创建和销毁,以下问题就能够很好的额理解了:

局部变量是如何创建的?
为什么局部变量不初始化内容是随机的?
函数调用时参数时如何传递的?传参的顺序是怎样的?
形参和实参的关系是什么呢?
函数调用结束后是如何返回的?

让我们一起走进函数栈帧的创建和销毁的过程中。

函数栈帧的创建和销毁解析

预备知识

什么是栈?

   

栈(stack )是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。
        在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push ),也可以将已经压入栈中的数据弹出(出栈,pop ),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out , FIFO )。就像叠成一叠的术,先叠上去的书在最下面,因此要最后才能取出。
        在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。
       
        在经典的操作系统中, 栈总是向下增长(由高地址向低地址)的 。
        在我们常见的i386 或者 x86-64 下,栈顶由成为 esp 的寄存器进行定位的。

认识相关寄存器和汇编指令

相关寄存器

寄存器名称    

 简介
eax 通用寄存器,保留临时数据,常用于返回值
ebx 通用寄存器,保留临时数据
ebp 栈底寄存器(Stack bottom)
esp 栈顶寄存器 (stack top)
eip 指令寄存器,保存当前指令的下一条指令的地址

 

相关汇编命令                          


汇编命令

解释

mov

数据转移指令(赋值)
push 数据入栈,同时 esp栈顶寄存器 也要发生改变
pop 数据弹出至指定位置,同时 esp栈顶寄存器 也要发生改变
sub 减法命令
add 加法命令
call 函数调用, 1 . 压入返回地址 2. 转入目标函数
jump 通过修改 eip ,转入目标函数,进行调用
ret 恢复返回地址,压入 eip ,类似 pop eip 命令



必备知识

每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。

这块空间的维护是使用了2个寄存器: esp 和 ebp , ebp 记录的是栈底的地址, esp 记录的是栈顶的地址。

第2点如图所示:


24adec6a38a648fda25f2482e76516a3.png


       3.函数栈帧的创建和销毁过程,在不同的编译器下创建和销毁是略有差异的,但是大体逻辑是相差不大的,当编译器越高级的时候,函数栈帧的封装越不容易看,所以编译器的环境采用vs2013


演示代码:

#include <stdio.h>
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int a = 3;
    int b = 5;
    int ret = 0;
    ret = Add(a, b);
    printf("%d\n", ret);
    return 0;
}


大体思路:

       每一个函数调用,都要在栈区创建一个空间


8bc604bee0a340f2b43080fc4f33c71e.png

       由于栈区使用内存的时候,每一次函数调用都要在栈区上分配空间,是先使用高地址,再使用低地址


打开调试窗口,接着打开调用堆栈



ba5e18c599114f6c8a2e342b3aca0e53.png

从调用堆栈看到,原来main函数也被调用了,那么它是被谁调用呢?


dac1aa82d4624ba280415ecbd19c35de.png

在VS2013中,main函数也是被其他函数调用的,调用逻辑如下:


4086dcd168954633a9b02a51498b9dae.png


反汇编代码:

右击鼠标,打开反汇编


int main()
{
//函数栈帧的创建
00BE1820 push ebp
00BE1821 mov ebp,esp
00BE1823 sub esp,0E4h
00BE1829 push ebx
00BE182A push esi
00BE182B push edi
00BE182C lea edi,[ebp-24h]
00BE182F mov ecx,9
00BE1834 mov eax,0CCCCCCCCh
00BE1839 rep stos dword ptr es:[edi]
//main函数中的核心代码
    int a = 3;
00BE183B mov dword ptr [ebp-8],3
    int b = 5;
00BE1842 mov dword ptr [ebp-14h],5
    int ret = 0;
00BE1849 mov dword ptr [ebp-20h],0
    ret = Add(a, b);
00BE1850 mov eax,dword ptr [ebp-14h]
00BE1853 push eax
00BE1854 mov ecx,dword ptr [ebp-8]
00BE1857 push ecx
00BE1858 call 00BE10B4
00BE185D add esp,8
00BE1860 mov dword ptr [ebp-20h],eax
    printf("%d\n", ret);
00BE1863 mov eax,dword ptr [ebp-20h]
00BE1866 push eax
00BE1867 push 0BE7B30h
00BE186C call 00BE10D2
00BE1871 add esp,8
    return 0;
00BE1874 xor eax,eax
}


1. _tmainCRTStartup函数栈帧的创建(调用main函数的函数)

 我们已经知道了,main函数也是被调用的,画出函数栈帧图详解一波:


       栈空间的使用是,由高地址到低地址,而main函数是被_tmainCRTStartup的,所以esp与ebp就维护当前的栈帧


20caf7627e744cca9b3f4c4e75a0028a.png


       ①执行push操作


       这时候F10按一下, 执行一下push让ebp这个地址压栈


49d47fa2d3134e53a0bf4870d338f24a.png


       怎么证明ebp压栈成功?


3a8425a5b9d14595a908a375b72be82c.png

       所以说,esp这个栈顶指针指向了ebp这个压栈的值:

051f42ae1ead411e8c2e1680b5fc536d.png



       ②接下来执行mov指令,就是把esp的值赋值给ebp

cae5481b77f44cc7bf74be3c8506004c.png


如下图:


fae8f153bc034236ab7c07ffe55b42f7.png


       ③然后执行sub指令,让esp减去0E4h,换成二进制就是228,,整体流程下图:


0eaf956fc2114b8792ac75094c55cdf3.png


       当①②执行完后,其实_tmainCRTStartup栈帧的空间已经开辟完毕,当③执行完后,调用了main函数,此时esp、ebp就预开辟好了一块空间给main函数,并维护该栈帧,如下图


2.函数栈帧的创建

       接着上文的内容,画出该图:


b39552290c9549c6bf3c534f61483cf0.png


       接着依次push三个寄存器ebx,esi,edi的值入栈中,esp往低地址处移动


1c965fd7b16a4ec6ada5d75e27d5f428.png


通过监视可以看一看


f5be9651a24b4afb8fa8509868f70ccf.png


画出图如下:


9c7dcd3f323d42359b62f8f64298a9f7.png


接下来看这四条指令:


2966d1c515fc428fb574073b583948b3.png


①lea edi, [ebp+FFFFFF1Ch]


解析:


[ebp+FFFFFF1Ch]显示符号名去掉,也就是[ebp-0E4h] (也就是和[esp - OE4h]是同一个位置)


lea - 加载有效地址,即将[ebp-0E4h]的地址加载到edi寄存器中,[ebp-0E4h] - 指向ebp(基准指针寄存器)上减去0E4h(232)个字节位置的内存单元


②mov ecx,39h(准确的次数)


解析:


将立即数 39h 复制到 ecx 寄存器中,使 ecx 寄存器的内容变为 39h(十进制的57)。


③mov  eax,0cccccccCh


解析:


这条指令将立即数 0cccccccCh 复制到 eax 寄存器中,使 eax 寄存器的内容变为 0cccccccCh


④rep stos dword ptr es : [edi]


解析:


rep 是重复前缀,用于指示指令要重复执行多次,执行的次数由 ecx 寄存器中的计数值决定。

stos 是字符串存储 (Store String) 的缩写,用于将数据存储到字符串中。

dword ptr 指明操作数的大小为双字(32位),用于指示要存储的数据的大小。

es:[edi] 是目标操作数,表示将数据存储到以 es 寄存器为段地址,edi 寄存器为偏移地址的内存位置。

       第④点整体来看:该指令的作用是将 eax 寄存器中的值重复写入到以 es:[edi] 为起始地址的内存位置。执行次数由 ecx 寄存器中的计数值确定。


   


       整体①②③④来看:


       要把edi这个位置开始(也就是[ebp-0E4h]的地址),向下空间的ecx(次数)放的39h这个值,这么多个dword(4个字节)的数据全部都改成0CCCCCCCCh,图解在下面:


e47479258bd149ddb5dbfab91dd57fae.png




        到这,main函数的开辟已经执行完了。


深度剖析c语言程序 -- 函数栈帧的创建和销毁(纯肝货)-2

https://developer.aliyun.com/article/1456965?spm=a2c6h.13148508.setting.30.2e124f0eZtymxB

相关文章
|
2月前
|
存储 自然语言处理 编译器
【C语言】编译与链接:深入理解程序构建过程
【C语言】编译与链接:深入理解程序构建过程
|
4月前
|
存储 算法 C语言
"揭秘C语言中的王者之树——红黑树:一场数据结构与算法的华丽舞蹈,让你的程序效率飙升,直击性能巅峰!"
【8月更文挑战第20天】红黑树是自平衡二叉查找树,通过旋转和重着色保持平衡,确保高效执行插入、删除和查找操作,时间复杂度为O(log n)。本文介绍红黑树的基本属性、存储结构及其C语言实现。红黑树遵循五项基本规则以保持平衡状态。在C语言中,节点包含数据、颜色、父节点和子节点指针。文章提供了一个示例代码框架,用于创建节点、插入节点并执行必要的修复操作以维护红黑树的特性。
110 1
|
13天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
32 5
|
4月前
|
编译器 C语言 计算机视觉
C语言实现的图像处理程序
C语言实现的图像处理程序
175 0
|
13天前
|
C语言
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性
C语言编程中,错误处理至关重要,能提升程序的健壮性和可靠性。本文探讨了C语言中的错误类型(如语法错误、运行时错误)、基本处理方法(如返回值、全局变量、自定义异常处理)、常见策略(如检查返回值、设置标志位、记录错误信息)及错误处理函数(如perror、strerror)。强调了不忽略错误、保持处理一致性及避免过度处理的重要性,并通过文件操作和网络编程实例展示了错误处理的应用。
45 4
|
12天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
36 1
|
13天前
|
网络协议 物联网 数据处理
C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势
本文探讨了C语言在网络通信程序实现中的应用,介绍了网络通信的基本概念、C语言的特点及其在网络通信中的优势。文章详细讲解了使用C语言实现网络通信程序的基本步骤,包括TCP和UDP通信程序的实现,并讨论了关键技术、优化方法及未来发展趋势,旨在帮助读者掌握C语言在网络通信中的应用技巧。
23 2
|
13天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
29 1
|
2月前
|
存储 文件存储 C语言
深入C语言:文件操作实现局外影响程序
深入C语言:文件操作实现局外影响程序
|
3月前
|
存储 编译器 程序员
C语言程序的基本结构
C语言程序的基本结构包括:1)预处理指令,如 `#include` 和 `#define`;2)主函数 `main()`,程序从这里开始执行;3)函数声明与定义,执行特定任务的代码块;4)变量声明与初始化,用于存储数据;5)语句和表达式,构成程序基本执行单位;6)注释,解释代码功能。示例代码展示了这些组成部分的应用。
114 10