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函數終止。

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/65120.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/65120.shtml
英文地址,請注明出處:http://en.pswp.cn/web/65120.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Transformer知識梳理

Transformer知識梳理 文章目錄 Transformer知識梳理什么是Transformer?語言模型遷移學習 Transformer結構注意力層原始結構 總結 什么是Transformer? 語言模型 Transformer模型本質上都是預訓練語言模型,大部分采用自監督學習(S…

第29天:PHP應用弱類型脆弱Hash加密Bool類型Array數組函數轉換比較

#知識點: 1、安全開發-原生PHP-弱類型脆弱 2、安全開發-原生PHP-函數&數據類型 3、安全開發-原生PHP-代碼審計案例 1、 和 兩個等號是弱比較,使用進行對比的時候,php解析器就會做隱式類型轉換,如果兩個值的類型不相等就會把兩…

STM32F1學習——編碼器接口

一、編碼器接口 編碼器接口可以接收正交編碼器的信號,根據編碼器旋轉產生的正交信號脈沖,通過硬件自動控制CNT值的自增或自減,從而指出編碼器的位置、旋轉方向和旋轉速度。 每個高級定時器和通用定時器都有一個編碼器接口,他們會占…

如何刪除 Docker 中的懸虛鏡像?

在 Docker 中,懸虛鏡像(Dangling Images)是指那些沒有 標簽 且沒有被任何容器使用的鏡像。這些鏡像通常是由于構建過程中生成的中間層鏡像或未正確清理的鏡像殘留。刪除懸虛鏡像可以釋放磁盤空間并保持 Docker 環境的整潔。 1. 列出懸虛鏡像…

網絡安全的學習與實踐經驗(附資料合集)

學習資源 在線學習平臺: Hack This Site:提供從初學者到高級難度的挑戰任務,適合練習各種網絡安全技術。XCTF_OJ:由XCTF組委會開發的免費在線網絡安全網站,提供豐富的培訓材料和資源。SecurityTube:提供豐…

ts是什么、tsc是什么、tsx是什么、jsx是什么、scss是什么

一、TS (TypeScript): TypeScript 是一種由微軟開發的開源編程語言,它是 JavaScript 的一個超集,增加了類型系統和對ES6及以后版本新特性的支持。TypeScript 旨在解決 JavaScript 開發中的可維護性、可擴展性和大型項目中的復雜性問題。它允許開發者在編…

行業商機信息付費小程序系統開發方案

行業商機信息付費小程序系統,主要是整合優質行業資源,實時更新的商機信息。在當今信息爆炸的時代,精準、高效地獲取行業商機信息對于企業和個人創業者而言至關重要。 一、使用場景 日常瀏覽:用戶在工作間隙或閑暇時間&#xff0c…

[Qt] 輸入控件 | Line | Text | Combo | Spin | Date | Dial | Slider

目錄 輸入類控件 1、Line Edit 錄入個人信息 使用正則表達式驗證輸入框的數據 驗證兩次輸入的密碼一致 切換顯示密碼 2、Text Edit 獲取多行輸入框的內容 驗證輸入框的各種信號 3、Combo Box 使用下拉框模擬麥當勞點餐 從文件中加載下拉框的選項 4、Spin Box 調整…

Flink CDC 自定義函數處理 SQLServer XML類型數據 映射 doris json字段方案

Flink CDC 自定義函數處理 SQLServer XML類型數據方案 1. 背景 因業務使用SQLServer數據庫,CDC同步到doris 數倉。對于SQLServer xml類型,doris沒有相應的字段對應, 可以使用json來存儲xml數據。需要進行一步轉換。從 flink 自定義函數入手…

C語言:cJSON將struct結構體與JSON互相轉換

文章目錄 struct 轉 jsonjson 轉 struct 文檔&#xff1a; https://github.com/DaveGamble/cJSON 項目結構 . ├── libs │ ├── cJSON.c │ └── cJSON.h └── main.c示例 struct 轉 json #include "libs/cJSON.h" #include <stdio.h>// defi…

JeeSite 快速開發平臺:全能企業級快速開發解決方案|GitCode 光引計劃征文展示

投稿人GitCode ID&#xff1a;thinkgem 光引計劃投稿項目介紹 JeeSite 快速開發平臺&#xff0c;不僅僅是一個后臺開發框架&#xff0c;它是一個企業級快速開發解決方案&#xff0c;后端基于經典組合 Spring Boot、Shiro、MyBatis&#xff0c;前端采用 Beetl、Bootstrap、Admi…

2025/1/4期末復習 密碼學 按老師指點大綱復習

我們都要堅信&#xff0c;道路越是曲折&#xff0c;前途越是光明。 --------------------------------------------------------------------------------------------------------------------------------- 現代密碼學 第五版 楊波 第一章 引言 1.1三大主動攻擊 1.中斷…

【架構設計(一)】常見的Java架構模式

常見的 Java 架構模式解析 在 Java 開發領域&#xff0c;選擇合適的架構模式對于構建高效、可維護且能滿足業務需求的軟件系統至關重要。本文將深入探討幾種常見的 Java架構模式&#xff0c;包括單體架構與微服務架構、分層架構與微服務架構的對比&#xff0c;以及事件驅動架構…

opencv與halcon的差距及改進方法

本文是直接問ai獲得的結果。 在我理解中&#xff0c;這是開源軟件與商業非開源工業軟件的普遍差距特點。 商業非開源工業軟件的人員、資金、時間投入是巨大的&#xff0c;開發人員也不是普通人普通水平。 一般情況下&#xff0c;試圖通過開源軟件改吧改吧就實現彎道超車&#x…

Java 內存溢出(OOM)問題的排查與解決

在 Java 開發中&#xff0c;內存溢出&#xff08;OutOfMemoryError&#xff0c;簡稱 OOM&#xff09;是一個常見且棘手的問題。相比于數組越界、空指針等業務異常&#xff0c;OOM 問題通常更難定位和解決。本文將通過一次線上內存溢出問題的排查過程&#xff0c;分享從問題表現…

AF3 AtomAttentionEncoder類解讀

AlphaFold3的AtomAttentionEncoder 類用于處理基于原子的表示學習任務。 源代碼: class AtomAttentionEncoderOutput(NamedTuple):"""Structured output class for AtomAttentionEncoder."""token_single: torch.Tensor # (bs, n_tokens, c_…

【51單片機零基礎-chapter3:按鍵:獨立按鍵|||附帶常見C語句.邏輯運算符】

將unsigned char var0;看作溝通二進制和十進制的橋梁 var是8位,初始為0000 0000; 同時可以進行十進制的運算 邏輯運算 位運算 & 按位與(有0則0) | 按位或(有1則1) ~ 按位非 ^ 按位異或(相同則1,不同為0) <<按位左移 >>按位右移 位運算符解釋: 0011 1100 <&…

游戲如何檢測iOS越獄

不同于安卓的開源生態&#xff0c;iOS一直秉承著安全性更高的閉源生態&#xff0c;系統中的硬件、軟件和服務會經過嚴格審核和測試&#xff0c;來保障安全性與穩定性。 據FairGurd觀察&#xff0c;雖然iOS系統具備一定的安全性&#xff0c;但并非沒有漏洞&#xff0c;如市面上…

在Lua中,Metatable元表如何操作?

Lua中的Metatable&#xff08;元表&#xff09;是一個強大的特性&#xff0c;它允許我們改變表&#xff08;table&#xff09;的行為。下面是對Lua中的Metatable元表的詳細介紹&#xff0c;包括語法規則和示例。 1.Metatable介紹 Metatable是一個普通的Lua表&#xff0c;它用于…

Python基于matplotlib實現樹形圖的繪制

在Python中&#xff0c;你可以使用matplotlib庫來繪制樹形圖&#xff08;Tree Diagram&#xff09;。雖然matplotlib本身沒有專門的樹形圖繪制函數&#xff0c;但你可以通過組合不同的圖形元素&#xff08;如線條和文本&#xff09;來實現這一點。 以下是一個簡單的示例&#…