01:C语言的本质
C语言的本质
- 1、ARM架构与汇编
- 2、局部变量初始化与空间分配
- 2.1、局部变量的初始化
- 2.1、局部变量数组初始化
- 3、全局变量/静态变量初始化化与空间分配
- 4、堆空间
1、ARM架构与汇编
ARM简要架构如下:CPU,ARM(能读能写),Flash(能读,写比较麻烦)。
2、局部变量初始化与空间分配
2.1、局部变量的初始化
CPU寄存器如下:
CPU中的特殊寄存器
①SP
:栈空间地址指针
②LR
:返回地址
③PC
:保存Flash的代码段的值,执行那个机器码就保存哪个代码的对应的值
执行如下代码时,单片机内部是怎样执行操作的?
int main()
{volatile int a = 10;volatile int b = 20;a = a+b;return 0;
}
C语言代码被编译为单片机能识别的机器码后,烧录进入单片机的Flash的代码段。
如下为c代码转换的汇编码和机器码
0x08000014 B50C PUSH {r2-r3,lr}17: volatile int a = 10;
0x08000016 200A MOVS r0,#0x0A
0x08000018 9001 STR r0,[sp,#0x04]18: volatile int b = 20;
0x0800001A 2014 MOVS r0,#0x14
0x0800001C 9000 STR r0,[sp,#0x00]19: a = a+b; 20:
0x0800001E E9DD1000 LDRD r1,r0,[sp,#0]
0x08000022 4408 ADD r0,r0,r1
0x08000024 9001 STR r0,[sp,#0x04]21: return 0;
0x08000026 2000 MOVS r0,#0x0022: }
0x08000028 BD0C POP {r2-r3,pc}
常见的汇编指令:
PUSH:压栈,一般情况将CPU的寄存器压入RAM栈空间例如:PUSH {r2-r3,lr}。表示将lr,r3,r2压入栈空间
MOVS:赋值,给CPU的寄存器赋值例如:MOVS r0,#0x0A。表示给r0寄存器赋值0x0A
STR:写入数据,将CPU的寄存器数据写入栈空间里面例如:STR r0,[sp,#0x00]。表示将r0的数据写入地址为sp + 0x00的空间
LDRD:读取2个数据,将栈空间的数据读取到CPU的寄存器里面例如:LDRD r1,r0,[sp,#0]。表示将sp+0x00地址的数据读取到r0,将sp+0x04地址数据读取到r1
LDR:读取1个数据
ADD:做加法, 例如:ADD r0,r0,r1。表示将r0 = r0 + r1
SUB:做减法例如:SUB sp,sp,#0x68。表示将sp = sp - 0x68
POP:出栈,将CPU的寄存器退出栈空间,用于栈空间的释放。例如:POP {r2-r3,pc}。表示将r2,r3,pc对应的栈空间释放。
①PUSH {r2-r3,lr}
。表示依次将寄存器lr,r3,r2中的数据压入栈的空间里面。而压栈的同时,sp
也会随着压栈而改变。如下图所示
【注】lr
寄存器里面的数据是返回地址,即在执行main函数之前,将ENDP
的地址保存在lr中。
如图:PUSH {r2-r3,lr}
此汇编对应的机器码为0x08000014 B50C
,当单片机执行完此机器码后,lr,r3,r2
的寄存器的值被保存到RAM的栈区空间里面。而sp(栈空间地址光标)会指向地址0x2000 FFF4。
【注】此时的r2和r3寄存器的值为空。
②volatile int a = 10
对应的汇编:MOVS r0, #0x0A
。表示将0x0A移入r0寄存器
STR r0, [sp,#0x04]
。表示将r0的数据写入(sp + 0x04)的地址存储空间。sp = 0x2000 FFF4,则sp + 4 = 0x2000 FFF8。所以将r0的数据写入到栈空间的r3的位置。
【注】0x2000 FFF8为什么代表r3的位置,而不是代表r2的位置喃?一般情况下一个存储空间是以较小的那个地址表示的
③volatile int b = 20
对应的汇编:MOVS r0, #0x14
。表示将0x0A移入r0寄存器
STR r0, [sp,#0x00]
。表示将r0的数据写入(sp + 0x00)的地址存储空间。sp = 0x2000 FFF4,则sp + 0 = 0x2000 FFF4。所以将r0的数据写入到栈空间的r2的位置。
④a = a + b
对应的汇编:LDRD r1, r0, [sp,#0]
。从栈区读取2个数据到r0,r1寄存器中。读取的起始地址为sp + 0 = 0x2000 FFF4。(r0接收地址sp + 0x00空间的数据,r1接收地址sp + 0x04空间的数据)即将b/0x14读取到r0,将a/0x0A读取到r1。
ADD r0, r0, r1
。表示将r1的数据加上r0的数据赋值r0。即r0 = 0x14 + 0x0A = 0x1E
STR r0, [sp,#0x04]
。表示将r0的数据写入(sp + 0x04)的地址存储空间。sp = 0x2000 FFF4,则sp + 0x04 = 0x2000 FFF8。所以将r0的数据写入到栈空间的r3的位置。
最终调试结果如下:
⑤return 0;
对应的汇编:MOVS r0,#0x00
。表示将r0寄存器的数据清零。
⑥栈的回收
对应的汇编:POP {r2-r3,pc}
。从栈中恢复寄存器 r2、r3 和 pc所对应栈空间的值,并且会自动调整栈指针 sp。最终sp指向0x20010000。表示之前使用的栈空间被回收。
【注】①低标号寄存器在栈空间对应低地址。进栈出栈都是。所以r2在栈空间的下面。②压栈时,先压进去sp在向下移动;出栈时,先出栈,sp在向上移动。
2.1、局部变量数组初始化
执行如下代码时,单片机内部是怎样执行操作的?
int main()
{volatile int a = 10;volatile char b[100];b[99] = 20;return 0;
}
如下为c代码转换的汇编码和机器码
0x08000014 B09A SUB sp,sp,#0x6817: volatile int a = 10; 18: volatile char b[100];
0x08000016 200A MOVS r0,#0x0A
0x08000018 9019 STR r0,[sp,#0x64]19: b[99] = 20;
0x0800001A 2014 MOVS r0,#0x14
0x0800001C F88D0063 STRB r0,[sp,#0x63]20: return 0;
0x08000020 2000 MOVS r0,#0x00
SUB sp,sp,#0x68
。表示sp = sp - 0x68。则sp = 0x2000 FFFC - 0x68 = 0x2000 FF98。其中0x68 = 104。则表示在栈区开辟了104个字节
3、全局变量/静态变量初始化化与空间分配
#include "main.h"volatile int g_a = 123;//全局变量
int main()
{static volatile int g_b = 321;//静态变量volatile int a = 10;volatile int b = 20;a = a+b;g_b = g_a + g_b;return 0;
}
如上代码包含g_a全局变量,g_b静态变量。如下为c代码转换的汇编码和机器码
0x08000154 B50C PUSH {r2-r3,lr}7: volatile int a = 10;
0x08000156 200A MOVS r0,#0x0A
0x08000158 9001 STR r0,[sp,#0x04]8: volatile int b = 20;
0x0800015A 2014 MOVS r0,#0x14
0x0800015C 9000 STR r0,[sp,#0x00]9: a = a+b;
0x0800015E E9DD1000 LDRD r1,r0,[sp,#0]
0x08000162 4408 ADD r0,r0,r1
0x08000164 9001 STR r0,[sp,#0x04]10: g_b = g_a + g_b;
0x08000166 4804 LDR r0,[pc,#16] ; @0x08000178
0x08000168 6800 LDR r0,[r0,#0x00]
0x0800016A 4904 LDR r1,[pc,#16] ; @0x0800017C
0x0800016C 6809 LDR r1,[r1,#0x00]
0x0800016E 4408 ADD r0,r0,r1
0x08000170 4902 LDR r1,[pc,#8] ; @0x0800017C
0x08000172 6008 STR r0,[r1,#0x00]11: return 0;
0x08000174 2000 MOVS r0,#0x0012: }
0x08000176 BD0C POP {r2-r3,pc}
综上:并未有机器码和汇编代码来初始化全局变量和静态变量。那么在内存中他们是怎样被初始化赋值的喃?
答案:将全局变量和局部变量需要被初始化的值保存在Flash的数据段里面。有多少个数据,在数据段里面就有多少个数据
有了数据,那全局变量和局部变量的内存又在哪里喃?又怎样将数据给到全局变量和局部变量喃?
答案:全局变量和静态变量依旧保存在RAM的里面,但不在是栈区。全局变量/静态变量由编译器分配的存储空间,不再是像局部变量由代码指令分配。如下图所示:Linker(链接器):将0x0800 0000的空间与0x2000 0000的空间链接在一起。
如上图:R/O base:0x0800 0000。表示的是Flash的数据段的起始地址。
R/W base:0x0200 0000。表示的是RAM中保存全局变量和静态变量的起始地址。
综上:
①全局变量/局部静态变量赋值和栈里面的局部变量不同,全局变量是先占用低地址空间,而局部变量是先占用高地址空间。
②全局变量是通过copy函数,将Flash里面的数据复制到全局变量和静态变量的内存里面。
③当 main 函数执行完毕时,虽然栈上的局部变量会被销毁,但是全局变量不会受到影响。全局变量在整个程序运行期间都存在,直到程序退出时才会被操作系统回收
【注】copy函数在启动文件里面,由程序员编写,且在调用main函数之前。调用完copy函数后在执行main函数。全局变量在程序启动时分配内存和初始化值,并在整个程序运行期间都保持有效。
综上为有初始值的全局变量和静态变量的内存分配情况(简称为:RW段),那若没有初始值/初始化为0的全局变量。依然会在Flash的数据段将数据0保存起来吗?显然浪费内存空间。
答案:没有初始值和初始值为0的全局变量,在Flash的数据段里面并未保存数据。但是编译器会在RAM里面给这些变量分配存储空间(简称:ZI段)。在调用main函数之间,调用memset函数将这些变量的存储空间清零。
4、堆空间
综上:①RAM中存在栈区:用于存储局部变量、函数参数、返回地址等。栈内存是自动管理的,随着函数调用和返回而分配和释放。②RAM也存在全局变量/静态局部变量区域。③RAM还存在堆区:堆区由用户调用mallo函数分配和管理,调用free函数进行释放。
堆区的空间不能在栈区里面分配。因为栈区空间会随着函数的结束而释放,是用户不可控制的。而堆区是不会随着函数的结束而释放。除非main函数终止。
而堆空间可以是全局变量区域。因为都是不会随着函数的结束而释放。除非main函数终止。
相关文章:

01:C语言的本质
C语言的本质 1、ARM架构与汇编2、局部变量初始化与空间分配2.1、局部变量的初始化2.1、局部变量数组初始化 3、全局变量/静态变量初始化化与空间分配4、堆空间 1、ARM架构与汇编 ARM简要架构如下:CPU,ARM(能读能写),Flash(能读&a…...
第1章:数据库基础
第1章:数据库基础 1.1 数据库概述 1.1.1 什么是数据库 数据库的定义数据库的发展历程数据库的重要性 1.1.2 关系型数据库简介 关系型数据库模型常见的关系型数据库关系型数据库的特点 1.1.3 MySQL在企业中的应用 Web应用电商平台金融系统大数据存储 1.2 数据…...
C++教程 | string类的定义和初始化方法
在C中,string是标准库中用于处理字符串的类,定义在 头文件中,它提供了方便、灵活的字符串操作功能。以下是一些常见的定义和初始化string对象的方法: 1. 默认初始化 可以直接定义一个空的string对象,语法如下&#x…...

React中的合成事件
合成事件与原生事件 区别: 1. 命名不一样,原生用纯小写方式,react用小驼峰的方式 原生:onclick React的:onClick 2. 事件处理函数的写法不一样 原生的是传入一个字符串,react写法传入一个回调函数 3.…...

[SMARTFORMS] 创建FORM
输入事务码SMARTFORMS进入表单开发界面,选中表单,自定义表单名称ZFS_DEMO_2025 点击"创建"按钮,跳转至"SAP表格设计器"页面 在"表格属性"填写表单描述、指定页格式和样式 在"表格接口"可以填写SMART…...
成都和力九垠科技有限公司九垠赢系统Common存在任意文件上传漏洞
免责声明: 本文旨在提供有关特定漏洞的深入信息,帮助用户充分了解潜在的安全风险。发布此信息的目的在于提升网络安全意识和推动技术进步,未经授权访问系统、网络或应用程序,可能会导致法律责任或严重后果。因此,作者不对读者基于本文内容所采取的任何行为承担责任。读者在…...

基于Python的考研学习系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
『SQLite』几种向表中插入数据的方法
向表中插入数据 INSERT INTO 语句用来给数据库中的某个表中新增数据行。 案例 直接根据基本语法插入数据插入时不用全部指定列名方式根据查询结果将数据插入另一张表中 注意 上述内容详讲见文章:SQLite的INSERT操作(内含案例)...

什么是Kafka的重平衡机制?
Kafka 的重平衛机制是指在消费者组中新增或删除消费者时,Kafka 集群会重新分配主题分区给各个消费者,以保证每个消费者消费的分区数量尽可能均衡。 重平衡机制的目的是实现消费者的负载均衡和高可用性,以确保每个消费者都能够按照预期的方式…...

pdf预览 报:Failed to load module script
pdf 预览报: Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of “application/octet-stream”. Strict MIME type checking is enforced for module scripts per HTML spec. 报错原因:…...

AI 角色扮演法的深度剖析与实践
📢📢📢 大家好,我是云楼Yunlord,CSDN博客之星人工智能领域前三名,多年人工智能学习工作经验,一位兴趣稀奇古怪的【人工智能领域博主】!!!😜&#…...

weblogic问题
安装weblogic单机后启动weblogic进程: 第一行: 这是一个 su 命令,用于切换到 weblogic 用户。 第二行: 这是 weblogic 用户的 bash shell 会话。 第三行: 这是启动 WebLogic 服务器的脚本。 第四行: 这是 …...
Qt仿音乐播放器:客户端唯一化
一、铺垫 1.我们采用共享内存来进行客户端的唯一化; 2.我刚看到的时候,就感觉,这是人想出来的吗?太绝了 二、实例 int main(int argc, char *argv[]) {QApplication a(argc, argv);QSharedMemory shareMemory("Widget&qu…...

ceph文件系统
ceph文件系统:高度可扩展,分布式的存储文件系统,旨在提高性能,高可靠性和高可用的对 象存储,块存储,文件系统的存储。使用分布式的算法保证数据的高可用和一致性。 ceph的组件 1、MON:ceph m…...

【数据结构-堆】力扣2530. 执行 K 次操作后的最大分数
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你的 起始分数 为 0 。 在一步 操作 中: 选出一个满足 0 < i < nums.length 的下标 i , 将你的 分数 增加 nums[i] ,并且 将 nums[i] 替换为 ceil(nums[i] / 3) 。 返回在 恰好…...
Java jdk8新特性:Stream 流
一. Stream 1. Stream也叫Stream流,是jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据。 2. 优势:Stream流大量的结合了lambda的语言风格来编程,提供了一种更加强大,更加简洁的方式操作集合…...

房产销售系统(源码+数据库+文档)
亲测完美运行带论文:文末获取源码 文章目录 项目简介(论文摘要)运行视频包含的文件列表(含论文)前端运行截图后端运行截图 项目简介(论文摘要) 随着科学技术的飞速发展,各行各业都在…...

Vue 项目自动化部署:Coding + Jenkins + Nginx 实践分享
前言 本文详细记录如何使用 Coding (以 Jenkinsfile 为核心) 和 Nginx 部署 Vue 项目,包含完整流程、配置细节及注意事项,为开发者提供一个高效的实践参考。 准备工作 这里借用一个优秀的开源项目做演示:芋道源码/yudao-ui-admin-vue2。 以…...

从零开始开发纯血鸿蒙应用之实现起始页
从零开始开发纯血鸿蒙应用 一、前言二、主要页面三、应用起始页四、MainPageContent 实现1、一级结构2、二级结构2.1、EmptyContent2.2、FileListContent2.2.1、ViewAction:2.2.2、EditAction2.2.3、DeleteAction2.2.4、ShareAction 五、载入起始页的时机五、总结 一…...

CG顶会论文阅读|《科技论文写作》硕士课程报告
文章目录 一、基本信息1.1 论文基本信息1.2 课程基本信息1.3 博文基本信息 二、论文评述(中英双语)2.1 研究问题(Research Problem)2.2 创新点(Innovation/Contribution)2.3 优点(Why this pape…...

【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...

【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...