当前位置: 首页 > news >正文

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. 报错原因&#xff1a…...

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 。 在一步 操作 中&#xff1a; 选出一个满足 0 < i < nums.length 的下标 i &#xff0c; 将你的 分数 增加 nums[i] &#xff0c;并且 将 nums[i] 替换为 ceil(nums[i] / 3) 。 返回在 恰好…...

Java jdk8新特性:Stream 流

一. Stream 1. Stream也叫Stream流&#xff0c;是jdk8开始新增的一套API(java.util.stream.*)&#xff0c;可以用于操作集合或者数组的数据。 2. 优势&#xff1a;Stream流大量的结合了lambda的语言风格来编程&#xff0c;提供了一种更加强大&#xff0c;更加简洁的方式操作集合…...

房产销售系统(源码+数据库+文档)

亲测完美运行带论文&#xff1a;文末获取源码 文章目录 项目简介&#xff08;论文摘要&#xff09;运行视频包含的文件列表&#xff08;含论文&#xff09;前端运行截图后端运行截图 项目简介&#xff08;论文摘要&#xff09; 随着科学技术的飞速发展&#xff0c;各行各业都在…...

Vue 项目自动化部署:Coding + Jenkins + Nginx 实践分享

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

从零开始开发纯血鸿蒙应用之实现起始页

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

CG顶会论文阅读|《科技论文写作》硕士课程报告

文章目录 一、基本信息1.1 论文基本信息1.2 课程基本信息1.3 博文基本信息 二、论文评述&#xff08;中英双语&#xff09;2.1 研究问题&#xff08;Research Problem&#xff09;2.2 创新点&#xff08;Innovation/Contribution&#xff09;2.3 优点&#xff08;Why this pape…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

宇树科技,改名了!

提到国内具身智能和机器人领域的代表企业&#xff0c;那宇树科技&#xff08;Unitree&#xff09;必须名列其榜。 最近&#xff0c;宇树科技的一项新变动消息在业界引发了不少关注和讨论&#xff0c;即&#xff1a; 宇树向其合作伙伴发布了一封公司名称变更函称&#xff0c;因…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...