GoLang函数栈的使用详细讲解
“纵有疾风来,人生不言弃”,这句话送给正在学习Golang的朋友们,也希望在阅读本文《GoLang函数栈的使用详细讲解》后,能够真的帮助到大家。我也会在后续的文章中,陆续更新Golang相关的技术文章,有好的建议欢迎大家在评论留言,非常感谢!
函数栈帧
我们的代码会被编译成机器指令并写入到可执行文件,当程序执行时,可执行文件被加载到内存,这些机器指令会被存储到虚拟地址空间中的代码段,在代码段内部,指令是低地址向高地址堆积的。堆区存储的是需要程序员手动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函数栈的使用详细讲解》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
Mysql中的日期时间函数小结
- 上一篇
- Mysql中的日期时间函数小结
- 下一篇
- 深入了解Go语言中web框架的中间件运行机制
-
- 单纯的雪糕
- 这篇博文太及时了,细节满满,写的不错,收藏了,关注师傅了!希望师傅能多写Golang相关的文章。
- 2023-03-07 19:03:49
-
- Golang · Go教程 | 28分钟前 |
- Go x509.CertPool.AddCert 怎样把自签名证书加入信任池
- 358浏览 收藏
-
- Golang · Go教程 | 28分钟前 |
- Go strconv.IsPrint 判断字符是否适合可打印日志的方案
- 349浏览 收藏
-
- Golang · Go教程 | 33分钟前 | Go教程 · 性能分析 Go 访问控制 ServeMux net/http/pprof 诊断入口
- Go net/http/pprof 怎样给诊断入口加访问控制
- 217浏览 收藏
-
- Golang · Go教程 | 39分钟前 | Go教程 · Go execution trace runtime/trace.StartRegion Region.End
- Go runtime/trace.StartRegion 怎样标记关键代码区间
- 112浏览 收藏
-
- Golang · Go教程 | 44分钟前 |
- Go io/fs FileInfo.ModTime 怎样读取虚拟文件时间
- 423浏览 收藏
-
- Golang · Go教程 | 13小时前 |
- Go parser.ParseComments 怎样让 AST 保留源码注释
- 413浏览 收藏
-
- Golang · Go教程 | 14小时前 |
- Go os.ReadFile 与 Open 的大文件选择
- 114浏览 收藏
-
- Golang · Go教程 | 14小时前 | go · bufio ·
- Go bufio.Reader.Discard 的部分跳过与 EOF 处理方案
- 199浏览 收藏
-
- Golang · Go教程 | 14小时前 |
- Go io.Pipe 的生产者消费者错误传播
- 170浏览 收藏
-
- Golang · Go教程 | 14小时前 |
- Go net/http MaxBytesReader 的请求体上限控制
- 474浏览 收藏
-
- Golang · Go教程 | 14小时前 |
- Go AppendQuoteRuneToASCII 怎样向切片追加字符转义文本
- 372浏览 收藏
-
- Golang · Go教程 | 14小时前 |
- Go cipher.NewGCMWithRandomNonce 怎样让 Seal 自动携带随机 nonce
- 304浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 757次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 754次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 836次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 803次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 635次使用
-
- GoLangchannel使用介绍
- 2022-12-22 440浏览

