函数战争(栈帧)之创建与销毁(c语言)(vs2022)

news2024/11/25 12:44:42


 首先,什么是函数栈帧?

        C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录,是编译器用来实现过程函数调用的一种数据结构。

以问答的方式解释编译器与解释器-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/forccct/article/details/135349101?spm=1001.2014.3001.5501

函数栈帧的作用

        函数栈帧是编译器用来实现函数调用的一种数据结构。在执行函数时,每个函数都会分配一个独立的栈帧,用于存储该函数的参数、局部变量、返回地址等信息。

        栈帧的作用在于保存函数的运行环境,使得函数执行时可以随时访问其所需的参数和局部变量。当函数被调用时,其栈帧被推入栈中,成为当前活动的栈帧。当函数执行完毕后,其栈帧从栈中弹出,并释放相关的内存空间。

         在C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。通过寄存器ebp和esp来维护当前的栈帧。

     函数栈帧是编译器用来实现函数执行环境的一种数据结构,它保存了函数的参数、局部变量和返回地址等信息,使得函数可以正确地执行并保持其运行环境。

学习函数栈帧的目的

       学习函数栈帧的目的在于深入理解程序的执行机制和编译器的工作原理。通过了解函数栈帧,程序员可以更好地理解函数调用时的内存布局、参数传递、局部变量管理以及异常处理等方面的知识。

        此外,理解函数栈帧也有助于提高程序的性能和可维护性。例如,通过合理使用栈帧,可以避免不必要的内存分配和释放操作,提高程序的执行效率。同时,了解栈帧也有助于在调试和优化程序时更好地分析程序的运行状态和性能瓶颈。

        学习函数栈帧可以帮助程序员更好地理解程序执行过程和编译器的工作原理,提高程序的性能和可维护性,并为解决复杂问题提供更有效的解决方案。

函数栈帧可以说是编程者的”内功“,修炼内功能更好的去理解和学习语


基础知识(相关的)

寄存器 

需要用到的寄存器的名字和功能
eaxEAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器
ebxEBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址
ecx是计数器(counter), 是重复(rep)前缀指令和LOOP指令的内定计数器
edx则总是被用来放整数除法产生的余数
ebp寄存器存放当前线程的栈底指针
esp

寄存器存放当前线程的栈顶指针

  1. EAX (Accumulator Register):
    • 通常用于算术和逻辑运算。
    • 在函数调用中,它经常用来返回结果。
    • 在一些系统调用和指令中,eax 也用来传递参数。
  2. EBX (Base Register):
    • 通常用作指向数据的基址指针。
    • 在某些系统调用和指令中,ebx 也用来传递参数。
    • 在某些情况下,它也可以用作通用寄存器。
  3. ECX (Count Register):
    • 通常用作计数器,特别是在循环和字符串操作中。
    • 在某些系统调用和指令中,ecx 也用来传递参数。
  4. EDX (Data Register):
    • 通常与eax一起用于32位乘法和除法运算。
    • 在某些系统调用和指令中,edx 也用来传递参数或返回额外的结果信息。
  5. EBP (Base Pointer):
    • 在函数调用中,它通常用来指向当前函数的栈帧基址。
    • 通过ebp,函数可以方便地访问其参数和局部变量,即使栈顶指针(esp)在函数执行过程中发生变化。
  6. ESP (Stack Pointer):
    • 指向栈顶的指针。
    • 当数据被压入栈时,esp减小;当数据从栈中弹出时,esp增加(在x86体系结构中,栈是向下增长的)。
    • 通过修改esp,函数可以分配和释放栈空间。

     详细见:寄存器的相关知识-CSDN博客”icon-default.png?t=N7T8https://blog.csdn.net/forccct/article/details/135316297?spm=1001.2014.3001.5501

汇编命令

汇编语言是一种低级语言,它直接与计算机的硬件和操作系统交互。汇编命令(或指令)是汇编语言中的基本单位,用于控制计算机执行特定的操作。以下是一些常见的汇编命令:

  1. 数据传送指令
    • MOV:将数据从一个位置移动到另一个位置。
    • PUSH:将数据压入栈中,同时更新栈顶指针。
    • POP:从栈顶弹出数据,同时更新栈顶指针。
  2. 算术运算指令
    • ADD:将两个数相加。
    • SUB:从第一个数中减去第二个数。
    • MUL 和 IMUL:无符号乘法和有符号乘法。
    • DIV 和 IDIV:无符号除法和有符号除法。
  3. 逻辑运算指令
    • AND:按位与操作。
    • OR:按位或操作。
    • XOR:按位异或操作。
    • NOT:按位非操作。
  4. 控制流指令
    • JMP:无条件跳转到一个指定的地址。
    • Jcc(如 JZJNZJEJNE 等):基于某个条件(如零标志位、符号标志位等)进行跳转。
    • CALL:调用一个子程序,保存返回地址。
    • RET:从子程序返回,恢复返回地址。
  5. 比较指令
    • CMP:比较两个操作数,设置相应的标志位。
  6. 堆栈操作指令(除了 PUSH 和 POP):
    • PUSHF 和 POPF:将标志寄存器压入栈中或从栈中弹出。
    • ENTER 和 LEAVE:用于高级语言过程/函数的栈帧设置和清除。
  7. 输入输出指令(与硬件或操作系统交互):
    • IN 和 OUT:从端口读取数据或向端口写入数据。
  8. 其他指令
    • NOP:无操作,通常用于填充或微调代码时序。
    • HLT:停止执行并等待外部中断。
    • CLI 和 STI:清除或设置中断标志。
    • 等等。

需要注意的是,具体的指令集依赖于特定的处理器架构(如 x86, ARM, MIPS 等),不同的架构有不同的指令集和寻址模式。上述指令主要基于 x86 架构,其他架构的指令可能会有所不同。

我们主要用到了

mov     push     pop     sub     add      call       jump      ret

栈和栈帧

            堆栈(stack)又称为堆叠,是计算机科学里最重要且最基础的数据结构之一,它按照FILO(First In Last Out,后进先出)的原则存储数据。(引用)

            但是我们为了简单理解函数栈帧,我们不需要很深入的了解,就只需要明白栈帧在内存中的存在形式...

             

       理解这个图.

       下面是高地址,上面是高地址,可以理解函数栈帧中,汇编命令从高地址向低地址访问改变和操控.

        这个栈,或者说这些值和其地址以这种形式存在于内存中,你访问就是内存.

推荐可以看一眼

汇编语言(不是很有必要学习,提供一个大佬的文章)

汇编语言入门教程 - 阮一峰的网络日志 (ruanyifeng.com)icon-default.png?t=N7T8https://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html


正式内容 

我们以vs2022为例子

 使用代码部分

int add(int x, int y)
{
    int n = 0;
    n = x + y;
    return n;
}

int main()
{
    int a = 1;
    int b = 2;
    int c = 5;
    c = add(a, b);
    printf("%d", c);
    return 0;
}

然后是打开反汇编

寄存器部分

首先是

00007FF79D985BB0  push        rbp  
00007FF79D985BB2  push        rdi  

这两个命令是 rbp 和 rdi 入栈

 00007FF79D985BB3  sub         rsp,148h  

申请148h字节内存

 00007FF79D985BBA  lea         rbp,[rsp+20h] 

00007FF79D985BBF  lea         rcx,[__0C5A5099_121@c (07FF79D992008h)] 

把rsp+20h的值塞入lea中,也就是为add申请20h字节的内存

 

    int a = 1;
00007FF79D985BCB  mov         dword ptr [rbp+4],1  
    int b = 2;
00007FF79D985BD2  mov         dword ptr [rbp+24h],2  
    int c = 5;
00007FF79D985BD9  mov         dword ptr [rbp+44h],5  

 分别为每个内存申请一个空间和带入数字

此时

00007FF79D985BE0  mov         edx,dword ptr [rbp+24h]  
00007FF79D985BE3  mov         ecx,dword ptr [rbp+4]  
00007FF79D985BE6  call        00007FF79D9813E3  
00007FF79D985BEB  mov         dword ptr [rbp+44h],eax 

 rbp+4 是a的地址 rbp+24h是b的地址 rbp+44h是c的地址 他们并不是连续存在的

上面的寄存器edx ecx 分别记录了a的值 c的值 并且记录在上面

现在是这个情况

然后是

 

 00007FF79D981DE0  mov         qword ptr [rsp+8],rcx  
00007FF79D981DE5  sub         rsp,38h  

 录入rcx 向上推rsp 现在理解 edx就是x的值  ecx就是y的值 这里的供add使用

这里是把rcx的值放到rsp上面+8地址内 rsp再向上移动38h个字节

 

 00007FF79D981DE9  mov         rax,qword ptr [rsp+40h]  
00007FF79D981DEE  mov         qword ptr [rsp+20h],rax

把rax移动到rsp+40h 把 rsp+20h移动到rax 

00007FF79D981DE0  mov         qword ptr [rsp+8],rcx  
00007FF79D981DE5  sub         rsp,38h  
    unsigned char *__DebuggerLocalJMCFlag = JMC_flag;
00007FF79D981DE9  mov         rax,qword ptr [rsp+40h]  
00007FF79D981DEE  mov         qword ptr [rsp+20h],rax  

    if (*JMC_flag && __DebuggerCurrentSteppingThreadId != 0 && __DebuggerCurrentSteppingThreadId == GetCurrentThreadId()) {
00007FF79D981DF3  mov         rax,qword ptr [rsp+40h]  
00007FF79D981DF8  movzx       eax,byte ptr [rax]  
00007FF79D981DFB  test        eax,eax  
00007FF79D981DFD  je          00007FF79D981E17  
00007FF79D981DFF  cmp         dword ptr [00007FF79D98D94Ch],0  
00007FF79D981E06  je          00007FF79D981E17  
00007FF79D981E08  call        qword ptr [00007FF79D991088h]  
00007FF79D981E0E  cmp         dword ptr [00007FF79D98D94Ch],eax  
00007FF79D981E14  jne         00007FF79D981E17  
NopLabel:
        __nop();
00007FF79D981E16  nop  
    }
}
00007FF79D981E17  add         rsp,38h 

经过上列过程开辟新函数的内容

00007FF79D985BE0  mov         edx,dword ptr [rbp+24h]  
00007FF79D985BE3  mov         ecx,dword ptr [rbp+4

00007FF79D985BE6  call        00007FF79D9813E3  
00007FF79D985BEB  mov         dword ptr [rbp+44h],eax 

其中

00007FF79D985BE6  call        00007FF79D9813E3  

 利用call调用add函数,把rbp+24h(edx) 和 rbp+4(ecx)的值在add中调用的返回值放入到eax中

然后把eax的值放入 rbp+44h(也就是c的地址) 完成了c的赋值


00007FF79D985BFD  xor         eax,eax  
}
00007FF79D985BFF  lea         rsp,[rbp+0000000000000128h]  
00007FF79D985C06  pop         rdi  
00007FF79D985C07  pop         rbp 

00007FF79D985C08  ret

这里是return 0;后面的

大致计算销毁占用的内存 然后返回系统

 然后结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1366476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++入门】函数模板类模板(泛型编程)

目录 前言 1. 泛型编程 2. 函数模板 2.1 概念 2.2 语法格式 2.3 原理 2.4 函数模板的实例化 隐式实例化 显示实例化 2.5 思考 2.6 模板参数的匹配原则 3. 类模板 3.1 类模板的定义格式 3.2 类模板的实例化 总结 前言 函数模板和类模板是C中的两种重要的模板形式,…

day11 有效的括号 删除字符串中的所有相邻重复项 逆波兰表达式求值

题目1:20 有效的括号 题目链接:20 有效的括号 题意 判断字符串是否有效,若有效: 1)左括号必须用相应的右括号 2)左括号的闭合顺序正确 ({)}顺序不正确,应该是({}) …

深度解析Nginx负载均衡算法及配置实例

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

Python3从零基础到入门(1)

目录 一、环境搭建 1.检测Python环境 2.下载安装Python环境 3.VSCode中配置Python环境 二、第一个程序 1.编码 2.输出 3.标识符 4.import 5.保留字 6.注释 7.缩进 三、变量和赋值 1.Python 中的变量 2.变量的赋值 3.多个变量赋值 四、基础数据类型 1.类型查看…

智能音箱喇叭杂音问题

智能音箱喇叭杂音问题 智能音箱生厂或出货过程会遇到多种喇叭播放有杂音的问题. 螺丝不匹配 智能音箱设备在生产过程,会有SPL测试喇叭失真,发现不良率8%的杂音问题. 分析原因是来料导入了新螺丝, 使用过程进入异物…

harmonyOS 时间选择组件(TimePicker)

本文 我们来说 TimePicker 时间组件 首先 我们搭一个最基本的组件骨架 Entry Component struct Index {build() {Row() {Column() {}.width(100%)}.height(100%)} }然后 在 Column 组件内 放一个 TimePicker进去 这里 我们就可以看到 一个时间的选择器了 DatePicker 捕获当前…

EPQ艾森克人格测试 49题(免费版)

艾森克人格提出人格的三个基本因素:性格内外向E、神经质N(也叫情绪性)和精神质P。这三个维度的不同程度组合,形成了独立的个体人格特征。 其中性格内外向维度是目前评估性格内向和外向的成熟量表,神经质和精神质为人格…

机器人技能学习-robosuite-0-入门介绍

文章目录 前言模块介绍实战案例1:从 demo 中创建自己的 env案例2:更换属于自己的物体 前言 资料太少、资料太少、资料太少,重要的事说三边,想根据自己实际场景自定义下机器人,结果发现无路可走,鉴于缺少参…

Maven初学Day1

1.Maven是什么? 是一个构建工具,可以自动化构建过程、任务。是一个项目模型 2.作为构建工具有什么特点 1.跨平台,可以在多个操作系统上使用 2.对外提供一致的操作接口 3.Maven官网 https://maven.apache.org/download.cgi 4.安装步骤 …

卷积神经网络|迁移学习-猫狗分类完整代码实现

还记得这篇文章吗?迁移学习|代码实现 在这篇文章中,我们知道了在构建模型时,可以借助一些非常有名的模型,这些模型在ImageNet数据集上早已经得到了检验。 同时torchvision模块也提供了预训练好的模型。我们只需稍作修改&#xf…

外延炉及其相关的小知识

外延炉是一种用于生产半导体材料的设备,其工作原理是在高温高压环境下将半导体材料沉积在衬底上。 硅外延生长,是在具有一定晶向的硅单晶衬底上,生长一层具有和衬底相同晶向的电阻率且厚度不同的晶格结构完整性好的晶体。 外延生长的特点&…

Linux实验——页面置换算法模拟

页面置换算法模拟 【实验目的】 (1)理解虚拟内存管理的原理和技术。 (2)掌握请求分页存储管理的思想。 (3)理解常用页面置换算法的思想。 【实验原理/实验基础知识】 存储器是计算机系统的重要资源之…

腾讯面试总结

腾讯 一面 mysql索引结构?redis持久化策略?zookeeper节点类型说一下;zookeeper选举机制?zookeeper主节点故障,如何重新选举?syn机制?线程池的核心参数;threadlocal的实现&#xff…

揭开JavaScript数据类型的神秘面纱

🧑‍🎓 个人主页:《爱蹦跶的大A阿》 🔥当前正在更新专栏:《VUE》 、《JavaScript保姆级教程》、《krpano》 ​ ​ ✨ 前言 JavaScript作为一门动态类型语言,其数据类型一直是开发者们关注的话题。本文将深入探讨Jav…

C语言算法(二分查找、文件读写)

二分查找 前提条件&#xff1a;数据有序&#xff0c;随机访问 #include <stdio.h>int binary_search(int arr[],int n,int key);int main(void) {}int search(int arr[],int left,int right,int key) {//边界条件if(left > right) return -1;//int mid (left righ…

MidTool的AIGC与NFT的结合-艺术创作和版权保护的革新

在数字艺术和区块链技术的交汇点上&#xff0c;NFT&#xff08;非同质化代币&#xff09;正以其独特的方式重塑艺术品的收藏与交易。将MidTool&#xff08;https://www.aimidtool.com/&#xff09;的AIGC&#xff08;人工智能生成内容&#xff09;创作的图片转为NFT&#xff0c…

数据库基础知识1

目录 数据库的使用 登录mysql 命令语法 常用命令 ​编辑 navicat建立连接 mysql授权管理命令 ​编辑mysql权限 数据导入导出 实例 数据导出 未登录 已经登录 导出导入的代码对比 ​编辑 导入导出的一个坑 python的导入导出 数据库基础知识 特点 需要掌握的程…

嵌入式——循环队列

循环队列 (Circular Queue) 是一种数据结构(或称环形队列、圆形队列)。它类似于普通队列,但是在循环队列中,当队列尾部到达数组的末尾时,它会从数组的开头重新开始。这种数据结构通常用于需要固定大小的队列,例如计算机内存中的缓冲区。循环队列可以通过数组或链表实现,…

使用Docker-compose快速构建Nacos服务

在微服务架构中&#xff0c;服务的注册与发现扮演着至关重要的角色。Nacos&#xff08;Naming and Configuration Service&#xff09;是阿里巴巴开源的服务注册与发现组件&#xff0c;致力于支持动态配置管理和服务发现。最近&#xff0c;一位朋友表达了对搭建一套Nacos开发环…

速卖通店铺销量飙升:掌握自养号测评(补单),轻松提升销售量

很多卖家在经营速卖通店铺时&#xff0c;都希望能提高自己店铺的曝光率。但对于一些新手卖家来说&#xff0c;可能不太清楚曝光率的具体含义以及如何提升。那么&#xff0c;让我们一起来探讨一下这个问题。 曝光率&#xff0c;简而言之&#xff0c;是指您的店铺和产品展示给顾…