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 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 什么是栈帧 -> 正文阅读

[数据结构与算法]什么是栈帧

栈帧浅析

什么是栈帧

引用百度百科中的解释:

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。

函数的每次调用,都有它自己独立的栈帧。栈帧中维持着函数调用所需要的各种信息,包括函数的入参、函数的局部变量、函数执行完成后下一步要执行的指令地址、寄存器信息等。

运行时栈

栈帧使用了栈这一数据结构,达到了后进先出(First In Last Out)的内存管理原则。不管是插入数据还是删除数据,都是在栈顶进行的。

x86-64的栈由高地址向低地址增长,寄存器rbp指向当前栈帧的底部(高地址),寄存器rsp指向当前栈帧的顶部(低地址)。数据压栈和出栈会修改rsp的值。通过push指令将数据存入栈中,同时64位系统中会对栈顶指针做减法操作rsp=rsp-8pop指令是push的逆操作,它将数据从栈中读取出来,同时64位系统中会对栈顶指针做加法操作rsp=rsp+8

当过程P调用过程Q时,

  1. 把实参压栈,cdeclgcc的默认调用约定,实参压栈顺序为从右至左

  2. 把返回地址(即P调用Q后的下一条指令地址)压入栈中,表示当Q返回后,P程序下一步要从哪条指令开始运行

  3. 开始调用Q,首先将P的栈底rbp压栈,然后栈顶rsp赋值给rbp,从而形成新的栈底地址。我们在看函数调用的汇编代码时经常看到的一段正是在做这个操作

    push    rbp
    mov     rbp, rsp
    
  4. 分配局部变量空间,开始具体执行Q函数的指令代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4UYEpKos-1638200341831)(C:\Users\86188\Desktop\1877984-20191225221512357-1441441103.png)]

转移控制

将控制从函数P转移到函数Q只需要将程序计数器(PC)设置为Q函数代码的起始位置。另外,稍后从Q返回时处理器还需要继续执行P的下一条指令A。在x86-64中,这个过程是用执行call Q指令来完成的。该指令将A的地址压栈,并将PC设置为Q的起始地址。

Q执行完成后弹出A地址这个过程是通过ret指令完成的,它会弹出地址A,并把PC设置为A

以一段函数调用为例

#include<iostream>
#include<stdint.h>

int32_t Add(int32_t x,int32_t y)
{
    int32_t z=0;
    z=x+y;
    return z;
}

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

函数很简单,我们在Compiler Explorer (godbolt.org)中编译,编译器版本为x86-64 GCC 11.2优化等级为O0,查看这段程序的汇编代码:

#include<iostream>
#include<stdint.h>

int32_t Add(int32_t x,int32_t y)      //  push    rbp
                                      //  mov     rbp, rsp
                                      //  mov     DWORD PTR [rbp-20], edi
                                      //  mov     DWORD PTR [rbp-24], esi
{
    int32_t z=0;                      //  mov     DWORD PTR [rbp-4], 0
    z=x+y;                            //  mov     edx, DWORD PTR [rbp-20]
                                      //  mov     eax, DWORD PTR [rbp-24]
                                      //  add     eax, edx
                                      //  mov     DWORD PTR [rbp-4], eax
    return z;                         //  mov     eax, DWORD PTR [rbp-4]
}                                     //  pop     rbp
                                      //  ret

int main()                       
{                                     
                                      //  push    rbp
                                      //  mov     rbp, rsp
                                      //  sub     rsp, 16
    int32_t a=10;                     //  mov     DWORD PTR [rbp-4], 10
    int32_t b=20;                     //  mov     DWORD PTR [rbp-8], 20
    int32_t ret=Add(a,b);             //  mov     edx, DWORD PTR [rbp-8]
                                      //  mov     eax, DWORD PTR [rbp-4]
                                      //  mov     esi, edx
                                      //  mov     edi, eax
                                      //  call    Add(int, int)
                                      //  mov     DWORD PTR [rbp-12], eax
    return 0;
}

分析上述汇编代码,当main函数调用Add时,执行了以下步骤(注意执行顺序):

Add(int, int):
    // 5. 将main函数的栈帧底部地址入栈保存 
	push    rbp
    // 6. 将此时的栈帧顶部地址作为Add函数的栈帧底部地址
    mov     rbp, rsp
    // 7. 获取形参x
    mov     DWORD PTR [rbp-20], edi
    // 8. 获取形参y
    mov     DWORD PTR [rbp-24], esi
    // 9. 初始化z=0
    mov     DWORD PTR [rbp-4], 0
    // 10. 将x和y分别保存至寄存器edx和eax,然后相加,结果保存在寄存器eax
    mov     edx, DWORD PTR [rbp-20]
    mov     eax, DWORD PTR [rbp-24]
    add     eax, edx
    // 11. 将寄存器eax中的x和y相加结果赋值给z
    mov     DWORD PTR [rbp-4], eax
    // 12. 将Add函数结果z保存至寄存器eax
    mov     eax, DWORD PTR [rbp-4]
    // 13. 将之前保存的main函数栈帧底部地址出栈并保存至rbp
    pop     rbp
    // 14. 相当于pop eip 将之前保存的mov     DWORD PTR [rbp-12], eax指令地址出栈并保存至eip
    ret

main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    // 1. 初始化a=10
    mov     DWORD PTR [rbp-4], 10
    // 2. 初始化b=20
    mov     DWORD PTR [rbp-8], 20
    // 3. 分别将a和b保存至寄存器eax和edx,再分别拷贝至寄存器edi和dsi
    mov     edx, DWORD PTR [rbp-8]
    mov     eax, DWORD PTR [rbp-4]
    mov     esi, edx
    mov     edi, eax
    // 4. 等价于两条命令
    //      ① push eip 将eip中存储的下一条指令地址压栈,即mov     DWORD PTR [rbp-12], eax这条指令
    //      ② jmp Add(int,int) 跳转至Add(int,int)
    call    Add(int, int)
    // 15. 将Add函数返回结果保存至ret
    mov     DWORD PTR [rbp-12], eax
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-11-30 15:52:09  更:2021-11-30 15:53:43 
 
开发: 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/26 13:34:04-

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