深入理解《hello world》是如何实现的

简介: 深入理解《hello world》是如何实现的

函数栈桢的创建和销毁

❤️ :热爱编程学习,期待一起交流。 🙏:博主是河牧院大一在读学生,水平有限,如发现错误,期待指点!(2466200050)

🌳:以下是我对main函数栈帧的创建与销毁一些拙见,期待大佬们指教。

前言


了解函数栈桢的目的是提高我们的基础知识功底,了解反汇编和内存,而不是会printf一个hello world,但不知道他是如何实现的,知其所以然。虽然学了这些并不能提高我们的算法能力,但看看到底是怎么运行的,有助于后期的学习,使我们向看代码是内存这个境界更近一步。

正如图上达克效应一样,横坐标代表技术。纵坐标代表自信。我们并没有想象中的那么优秀,要学会从他人眼里看到自己的影子。也许你现在处于愚昧之巅,也许你处在绝望之谷,不过这都是正常的,只要我们认清自己,持续输出,一定会走向开悟之坡。


C语言是由函数构成的

C语言是由函数为单元模块构成的,不信的话,可以想象一下,每一次我们用C语言编写程序的时候,都需要写一个main()。而main()就是一个函数。

就拿最简单的打印一句hello world来说,不就是在main函数里面的的吗。

所以我们了解函数栈帧的创建和销毁,其实就在研究一个代码是如何实现的。

我们在这里要用到汇编代码来演示函数栈帧的创建与销毁。

栈帧概念

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


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


首先应该明白,栈帧是存放在内存中的栈区的。栈帧是栈区分配给进程的内存区。


栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。


寄存器

ebp:栈底指针

esp:栈顶指针

esp和ebp是用来维护函数的。


寄存器有很多,但这里我们主要用到一下两种寄存器,ebp和esp。

这两个寄存器存放的是地址。用来维护函数栈帧。所以我们可以把它俩个可以叫做指针。指针不就是用来存放地址的嘛。

我们平时用C语言写的每一个程序都包含了函数,而每一个函数的调用,都要在栈区创建一个空间。这个空间区域就叫做函数栈帧。而ebp和esp就是用来维护这片区域的。


hello world是如何实现

这里我们需要明白

main函数是一个程序的入口,main函数有且只能有一个。

我们在进入这个程序的时候,首先需要调用main函数,你没听错,就是调用它

那么是谁调用的main函数呢?它是被一个简称叫做CRT的函数所调用的。(CRT是我自己命名的简称,我只要知道main函数是被它调用的就好了。)

#include<stdio.h>
int main()
{
  printf("hello world");
  return 0;
}
  • 看到这段在main函数里面的代码。有人就乐了,不就是一个printf就可以打印出来了吗?
  • 是的,没错,的确是一个printf就打印出来了。
  • 但是在执行printf这句语句的时候,编译器要做大量的工作。这里的工作就是指函数栈帧的创建

我们转到汇编代码

  • 这里我用的是vs2019编译器下转到反汇编查看的。
  • 红色的框就是ebp指针和esp指针从维护CRT函数转变到维护main函数的过程。
  • 绿色的框就是我们程序代码。

🌳 main函数栈帧的创建(开始调用main函数)

下面是esp和ebp维护CRT函数栈帧的图片

首先进行第一条指令


  • push的意思就是压栈,就是将ebp放到栈顶。
  • 注意:这是为了将来main函数返回之后,esp寄存器和ebp寄存器还能来回来维护CRT函数的,这里push的ebp就是为以后调用完返回CRT函数埋下的伏笔。

* 压栈的同时esp指针也会随着向上走。CRT函数的栈帧也随之增加

* 接着执行第二条指令

mov是move的缩写,意思是将esp赋值给ebp,实质是将esp里面的地址放到ebp里面。(也就是两个指针指向了同一位置)

注意:此时ebp和esp没有在维护CRT函数了,但他的函数栈帧没有销毁,因为栈空间的使用只能先销毁低地址,再销毁高地址。即图中的从上往下销毁。

  • 第三条指令

  • sub是减法的英文。意思是将esp这个地址减去C0这个16进制数字。C0其实就是十进制的192.可以在电脑上打开计算器,切换为程序员模式进行计算。(esp-192)
  • h是一个标识符,不用管它
  • * esp-192就是esp指针从低地址移向了高地址。而此时就呈现出来了一个main函数栈帧的雏形。ebp和esp之间的就是他的雏形。

* 接下来执行第四,五,六条指令。(可以不用管这三条指令)

  • 这里是连续三次(push)压栈,为了保存这三个寄存器。压栈的同时,esp指针位置随之上升。main函数的栈帧也随之扩大。

  • 到这里main函数的函数栈帧初步创建成功。

🌳 main函数栈帧的初始化

  • 这四步骤就是将ebp到esp之间的空间初始化为cc cc cc cc。
  • 这些cc cc cc cc是随机值。

* 然后执行这两条命令

  • 这里的call是调用的意思。就是调用printf函数来打印hello world。这里我就不赖细究printf函数怎么调用了。触及到博主目前知识水平极限了。
  • 保存002e1339这个地址的作用是为了调用完printf函数后返回主调函数,执行下一条指令。

🌳函数栈帧的销毁

printf函数栈帧的销毁

  • 调用完printf函数后,printf函数它的栈帧就要销毁了。
  • add的意思是让esp加4,即栈顶指针向下移动,来实现栈帧销毁。

  • 这个xor指令的意思为异或,两个相同的数异或就为0(实质是设置返回值为0)


main函数的栈帧销毁

  • main函数栈帧的销毁遵循后进先出的原则
  • 首先连续执行三个pop(弹出),把三个寄存器还原回去。


  • 我们让esp加上0C0h,那么esp将会往下移动到ebp的位置。
  • 官方语言是:降低栈顶esp的位置,此时局部变量空间被释放。
  • 此时main函数的栈帧就销毁了。


  • 接着的话esp和ebp指针会重新去维护调用main函数的函数栈帧

总结

每一次的函数调用都要在栈区开辟相应的栈帧空间。并将空间的内容进行初始化。

为什么平时写程序时没有赋值的值都是随机值?因为局部变量就是在函数栈帧里创建的。局部变量的值是我们初始化的cc cc cc cc。cc cc cc cc就是随机值

函数调用是怎么返回的?我们首先push了一个ebp寄存器,这样调用完之后就可以根据ebp这个地址返回了。

如果你觉得我的文章对你有帮助🎉欢迎关注🔎点赞👍收藏⭐️留言📝。


相关文章
|
11月前
|
机器学习/深度学习 人工智能 算法
【AI系统】AI的历史、现状与理论基础
人工智能(AI)作为一门跨学科的研究领域,其目标是模拟、延伸和扩展人的智能。本文旨在概述AI的历史发展、当前趋势以及理论基础,为读者提供一个系统的视角。
365 0
|
弹性计算 Kubernetes 安全
基于 Traefik 的 Basic Auth 配置
基于 Traefik 的 Basic Auth 配置
|
机器学习/深度学习 安全 分布式数据库
【阿里云 CDP 公开课】 第五讲:如何迁移 CDH/HDP 到 CDP
本文整理自 Cloudera 生态资深解决方案工程师王雪峰,分享的《如何迁 移CDH_HDP到CDP》,主要分为 CDP升级概述、迁移升级指南两个部分。
【阿里云 CDP 公开课】 第五讲:如何迁移 CDH/HDP 到 CDP
|
自然语言处理 搜索推荐 算法
word2vec模型原理及实现词向量训练案例(二)
word2vec模型原理及实现词向量训练案例
720 0
word2vec模型原理及实现词向量训练案例(二)
|
Web App开发 安全 Ubuntu
【Linux】这些是用于编程的最佳Linux发行版
您是程序员、工程师还是系统管理员,希望最大限度地利用您的计算机?然后,您需要最好的Linux发行版进行编程。我们选择Fedora是因为它突破了Linux的限制,拥有最新的开源软件。
模型驱动是什么意思?底层原理是什么?
模型驱动是什么意思?底层原理是什么?
1209 0
|
移动开发 弹性计算 前端开发
开年见礼!云开发生态激励计划上线,近万元补贴等新年福利请查收
工欲善其事,必先得其器,一款得心应手的编程工具,对于程序员来说无疑是效率神器,可以令开发工作事半功倍。阿里云云开发平台自发布1年半以来,为十几万开发者带来的效率加成和提升。今年云开发平台进一步升级演化,除了Web应用以外,天猫精灵智能应用平台、钉钉开放平台、支付宝开放平台将云开发平台集成到研发链路中,满足IoT语音技能、小程序、H5应用等开发者低门槛应用开发的需求。
开年见礼!云开发生态激励计划上线,近万元补贴等新年福利请查收
|
分布式计算 Hadoop Java
hadoop配置
hadoop配置
353 0
|
SQL 关系型数据库 Linux
信创迁移适配预研-达梦数据库DM8服务与客户端工具安装使用
信创迁移适配预研-达梦数据库DM8服务与客户端工具安装使用
750 0
信创迁移适配预研-达梦数据库DM8服务与客户端工具安装使用