返回顶部
首页 > 资讯 > 后端开发 > GO >GoLang函数栈的使用详细讲解
  • 266
分享到

GoLang函数栈的使用详细讲解

Go函数栈GoLang函数栈 2023-02-02 12:02:17 266人浏览 八月长安
摘要

目录函数栈帧寄存器函数栈帧 我们的代码会被编译成机器指令并写入到可执行文件,当程序执行时,可执行文件被加载到内存,这些机器指令会被存储到虚拟地址空间中的代码段,在代码段内部,指令是低

函数栈帧

我们的代码会被编译成机器指令并写入到可执行文件,当程序执行时,可执行文件被加载到内存,这些机器指令会被存储到虚拟地址空间中的代码段,在代码段内部,指令是低地址向高地址堆积的。堆区存储的是需要程序员手动alloc并free的空间,需要自己来控制。

虚拟内存空间是对存储器的一层抽象,是为了更好的来管理存储器,虚拟内存和存储器之间存在映射关系。

如果在一个函数中调用了另外一个函数,编译器就会对应生成一条call指令,当call指令被执行时,就会跳转到被调用函数入口处开始执行,而每个函数的最后都有一条ret指令,负责在函数结束后跳回到调用处继续执行。

call 指令做了两件事,将下一条指令的地址入栈,这就是IP寄存器中存储的值,第二,跳转到被调用函数入口处执行。

函数执行时需要有足够的内存空间用来存储参数,局部变量,返回值,这块空间对应的就是栈,栈区是从高地址向低地址生长的,且先进后出。分配给函数的栈空间被称为函数栈帧。

C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

寄存器

ESP寄存器:ESP即 Extended stack pointer 的缩写,直译过来就是扩展的栈指针寄存器。SP是16位的,ESP是32位的,RSP是64位的,存放的都是栈顶地址。

EBP寄存器:EBP即 Extended base pointer 的缩写,直译过来就是扩展的基址指针寄存器。该指针总是指向当前栈帧的底部。

IP寄存器:指令指针,它指向代码段中的地址,是一个16位专用寄存器,它指向当前需要取出的指令字节,也就是下一个将要执行的指令在代码段中的地址。

eax:累加(Accumulator)寄存器,常用于函数返回值

ebx:基址(Base)寄存器,以它为基址访问内存

ecx:计数器(Counter)寄存器,常用作字符串和循环操作中的计数器

edx:数据(Data)寄存器,常用于乘除法和I/O指针

esi:源地址寄存器

edi:目的地址寄存器

esp:堆栈指针

ebp:栈指针寄存器

当然,以上功能并未限制寄存器的使用,特殊情况为了效率也可作其他用途。

这八个寄存器低16位分别有一个引用别名 ax, bx, cx, dx, bp, si, di, sp,

其中 ax, bx, cx, dx, 的高8位又引用至 ah, bh, ch, dh,低八位引用至 al, bl, cl, dl

在 64-bit 模式下,有16个通用寄存器,但是这16个寄存器是兼容32位模式的,

32位方式下寄存器名分别为 eax, ebx, ecx, edx, edi, esi, ebp, esp, r8d – r15d.

在64位模式下,他们被扩展为 rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp, r8 – r15.

其中 r8 – r15 这八个寄存器是64-bit模式下新加入的寄存器。

我们看到CPU在执行代码段中的指令,而这当中又伴随着内存的分配,于是在函数栈帧上就会有相应的变化。

int add(int a, int b)
{
    int c = 4;
    c = a + b;
    return c;
}
int main()
{
    int a = 1;
    int b = 2;
    int sum = 3;
    sum = add(a, b);
    return 0;
}

生成的汇编代码的方式

1、使用 GCc + objdump

gcc -save-temps -fverbose-asm -g -o b testasm.c
objdump -S --disassemble b > b.objdump

2、使用第三方网站来生成,进入 https://Godbolt.org/,选择语言为C,编译器为x86-64 gcc 12.2,粘贴进你的代码,就能看到汇编代码,如下

add:
        push    rbp
        mov     rbp, rsp
        mov     DWord PTR [rbp-20], edi
        mov     DWORD PTR [rbp-24], esi
        mov     DWORD PTR [rbp-4], 4
        mov     edx, DWORD PTR [rbp-20]
        mov     eax, DWORD PTR [rbp-24]
        add     eax, edx
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 1
        mov     DWORD PTR [rbp-8], 2
        mov     DWORD PTR [rbp-12], 3
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    add
        mov     DWORD PTR [rbp-12], eax
        mov     eax, 0
        leave
        ret

从main开始解读

// 此时rbp存储的还是上一层函数(调用者)的栈基地址,将rbp的值入栈保存起来,因为main函数也是被其他函
// 数调用的,运行完main之后还得回到那个函数体中去。这里的地址指的是指令的地址,是代码段中的位置。
// push指令会使rsp下移。
push    rbp 
// 此时rbp存储的还是上一个函数的基地址,而rsp则已经游走到了main函数这里,mov指令将rsp中存储的地址传递
// 给rbp,也就意味着执行完之后rbp和rsp都处于main函数的开始位置,称为初始化操作。
mov     rbp, rsp
// rsp下移16,就是分配栈空间
sub     rsp, 16

// DWORD 为双字,即四个字节,PTR为指针的意思,此句意为在rbp向下偏移4个字节的这段栈内存中存储0
// a
mov     DWORD PTR [rbp-4], 1
// b
mov     DWORD PTR [rbp-8], 2
// sum
mov     DWORD PTR [rbp-12], 3
// 将参数从右到左,依次存起来,此处存到了 edx和eax,并拷贝了一份到esi和edi。
mov     edx, DWORD PTR [rbp-8]`
mov     eax, DWORD PTR [rbp-4]`
mov     esi, edx`
mov     edi, eax`

 // 执行call指令
// 注意,call会使CPU跳入到add的栈帧中去,那么执行完之后,我们需要跳回到被调用处继续向下执行,由
// 最前面的push指令我们已经把调用者的栈基存了下来,可是我们还要精确到具体是回到哪个指令,这就是call
// 指令的额外工作,它会先将IP入栈(push ip),因为IP中存的就是下一条指令(mov DWORD PTR [rbp-12], eax)
// 的地址,然后再去跳转(jmp),将add函数的第一条指令写入IP,此后就进入add函数栈帧。
call    add

// cpu执行完运算后会将结果存储在寄存器中,至于它会把结果存储在那个寄存器,这个由编译器编译出的指令
// 决定的,由add函数的指令来看,它选择了eax
// rbp-12 为sum的位置,这条指令将eax寄存器的值赋值给sum
mov     DWORD PTR [rbp-12], eax
// 将eax置0,也就是main的返回值
mov     eax, 0
// 意为 mov rsp, rbp 和 pop rbp 的组合
// 此时rbp为main函数的栈基,rsp为main函数的末尾了,将rbp赋值给rsp,于是它们都指向main函数的栈基,上
// 面解释过,rbp寄存器存储的地址指向的栈上的空间存储的还是一个地址,此地址指向调用者的栈基,
// pop rbp 将栈顶rsp的数据送入rbp,就意味着之后就回到了调用者的栈帧了,同时pop会伴随着rsp的上移,
// 于是rsp来到了EIP的位置。
leave
// 相当于 pop ip
// 此函数执行完需要跳回到调用者并继续执行下一条指令,由于call的时候已经将下一条指令的地址入栈了,所以
// 此处值需要将其弹出即可。
ret

到此这篇关于golang函数栈的使用详细讲解的文章就介绍到这了,更多相关Go函数栈内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: GoLang函数栈的使用详细讲解

本文链接: https://lsjlt.com/news/193975.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • GoLang函数栈的使用详细讲解
    目录函数栈帧寄存器函数栈帧 我们的代码会被编译成机器指令并写入到可执行文件,当程序执行时,可执行文件被加载到内存,这些机器指令会被存储到虚拟地址空间中的代码段,在代码段内部,指令是低...
    99+
    2023-02-02
    Go函数栈 GoLang函数栈
  • Vuerender函数使用详细讲解
    目录Dom什么是render函数render函数的返回值(VNode)template与render简单的render函数什么时候使用RenderDom 在浏览器中通过js来操作DO...
    99+
    2023-01-17
    Vue render函数 Vue render
  • C语言详细讲解qsort函数的使用
    目录qsort1.int型2.float型3.struct型qsort 功能:Performs a quick sort.(快速排序)参数:void qsort( void *bas...
    99+
    2024-04-02
  • C语言超详细讲解getchar函数的使用
    目录一、getchar 函数二、缓冲区1、什么是缓冲区2、为什么要存在缓冲区3、缓冲区的类型4、缓冲区的刷新三、getchar 函数的正确使用1、getchar 的换行问题2、get...
    99+
    2024-04-02
  • C语言超详细讲解函数栈帧的创建和销毁
    目录1、本节目标2、相关寄存器3、相关汇编指令4、什么是函数栈帧5、什么是调用堆栈6、函数栈帧的创建和销毁(1)、main函数栈帧的创建与初始化(2)、main函数的核心代码(3)、...
    99+
    2024-04-02
  • Golang创建构造函数的方法超详细讲解
    目录组合字面量自定义构造函数从构造函数返回错误interface构造函数最佳实践基本构造函数主包类型多个构造函数组合字面量 组合字面量是最直接方式初始化Go对象,假设定义了Book类...
    99+
    2023-01-28
    Go创建构造函数 Go构造函数
  • 数据结构:栈和队列(详细讲解)
    🎇🎇🎇作者: @小鱼不会骑车 🎆🎆🎆专栏: 《数据结构》 🎓🎓...
    99+
    2023-09-14
    数据结构 java 算法
  • 详细理解函C语言的函数栈帧
    目录一、函数栈帧的创建1.寄存器2.函数栈帧3.函数中调用函数二、函数栈帧的销毁总结一、函数栈帧的创建 1.寄存器 一般来说,计算机中的寄存器有六种 分别是:eax, ebx, e...
    99+
    2024-04-02
  • mysql日期函数TO_DAYS()函数的详细讲解
    目录背景:TO_DAYS()函数总结背景: 今天在项目里看到的sqlserver的days()函数,计算从0年开始到给定时间的天数,现在要修改为mysql语法,有个很奇怪的现象奥,同样都是今天2021-11-30,sql...
    99+
    2024-04-02
  • C++详细讲解常用math函数的用法
    目录1、fabs(double x)2、floor(double x)ceil(double x)3、pow(double x,double n)4、sqrt(double x)5、...
    99+
    2024-04-02
  • MySQL窗口函数OVER使用示例详细讲解
    目录窗口函数测试数据表及数据窗口函数空窗口窗口中只有 ORDER BY窗口中只有 PARTITION BY 时同时有 PARTITION BY 与 ORDER BY窗口函数 OVER (PARTITION BY xxx ...
    99+
    2023-01-05
    MySQL 窗口函数 窗口函数OVER MySQL OVER函数
  • python中Path函数讲解【详细】
    文章目录 1、Path函数的基本功能2、常见用法2.1 表示路径2.2 路径的拼接和分解2.3 获取路径 1、Path函数的基本功能 使用pathlib模块来处理文件和文...
    99+
    2023-09-01
    深度学习 图像处理 python
  • JavaScala偏函数与偏应用函数超详细讲解
    目录偏函数isDefinedAtorElseandThenapplyOrElse偏应用函数偏函数 偏函数(Partial Function),是一个数学概念它不是"函数&q...
    99+
    2023-05-14
    Java Scala偏函数 Java Scala偏应用函数
  • 详细讲解Golang请求包
    Golang是一门越来越受欢迎的编程语言,它在服务器端的应用领域日益增多。开发者们都知道,网络请求在服务器端开发中是必不可少的部分,那么Golang中怎么进行网络请求呢?本文将详细讲解Golang请求包,让大家更好地掌握Golang网络请求...
    99+
    2023-05-14
  • C++超详细讲解函数对象
    目录一、客户需求二、存在的问题三、解决方案四、函数对象五、小结一、客户需求 编写一个函数 函数可以获得斐波那契数列每项的值每调用一次返回一个值函数可根据需要重复使用 下面来看第一个...
    99+
    2024-04-02
  • C++超详细讲解函数重载
    目录1 函数重载的定义2 构成函数重载的条件3 编译器调用重载函数的准则4 函数重载的注意事项4.1 避开重载带有指定默认值参数的函数4.2 注意函数重载遇上函数指针4.3 C++编...
    99+
    2024-04-02
  • C++超详细讲解析构函数
    目录特性析构函数处理自定义类型编译器生成的默认析构函数特性 析构函数是特殊的成员函数 特征如下: 析构函数名是~类名;无参数无返回值;一个类有且只有一个析构函数;对象声明周期结束,编...
    99+
    2024-04-02
  • C++超详细讲解构造函数
    目录类的6个默认成员函数构造函数特性编译器生成的默认构造函数成员变量的命名风格类的6个默认成员函数 如果我们写了一个类,这个类我们只写了成员变量没有定义成员函数,那么这个类中就没有函...
    99+
    2024-04-02
  • Pandas绘图函数超详细讲解
    目录简介条形图折线图箱线图直方图饼图散点图和六边形分箱图简介 method绘图类别method绘图类别'line'折线图[默认使用]'area'堆叠面...
    99+
    2022-12-20
    Pandas绘图函数 Python绘图函数
  • Golang函数的回调函数应用讲解
    Golang作为一门高效的编程语言,其函数的回调函数应用极为重要,因此在本篇文章中,我们将深入讲解Golang函数的回调函数应用的相关知识。一、什么是回调函数?回调函数就是函数指针,它作为参数直接传给其他函数。当这个参数函数执行完毕后,再回...
    99+
    2023-05-16
    Golang函数 回调函数 应用讲解
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作