首先我們先查看官方對于Cortex-M3預定義的存儲器映射
1.存儲器映射
1.1?Cortex-M3架構的存儲器結構
內部私有外設總線:即AHB總線,包括NVIC中斷,ITM硬件調試,FPB, DWT。
外部私有外設總線:即APB總線,用于內部“外設”的操作(SPI,IIC,USART)等,也用于些片上APB外設到APB私有總線上(SD卡,LCD顯示屏)的訪問。
他們的物理地址關系如下圖所示:
2.RAM和FLASH的基本概念與作用
從上面的描述可以知道,其實Cortex-M3分配的地址很多都是不允許修改的,能修改的只有代碼區的FLASH和上電后代碼運行的片上SRAM區的RAM,我們這章就著重講一下這兩個地方。
1.2.1RAM的基本概念與作用
RAM(隨機存取存儲器)在STM32中主要用于存儲運行時的數據和變量。它是易失性存儲器,斷電后數據會丟失。RAM的作用包括:
- 存儲程序運行時的臨時數據,如局部變量、堆棧和動態分配的內存。
- 提供快速的數據訪問,支持CPU的高效運行。
- 用于存儲中斷向量表、全局變量和靜態變量。
1.2.2?FLASH的基本概念與作用
FLASH存儲器在STM32中用于存儲程序代碼和常量數據。它是非易失性存儲器,斷電后數據不會丟失。FLASH的作用包括:
- 存儲固件程序代碼,包括主程序、中斷服務程序和庫函數。
- 存儲常量數據,如查找表、配置參數和字符串。
- 支持程序更新和固件升級,通過編程接口可以擦除和寫入數據。
1.2.3?程序的典型內存分布和用途
內存分布:
+------------------+ 0x20005000 (RAM 結束)
| Heap | 動態分配(malloc)
+------------------+
| Stack | 棧(局部變量、返回地址)
+------------------+
| .bss | 未初始化數據(RAM)
+------------------+
| .data | 已初始化全局變量(RAM)
+------------------+ 0x20000000 (RAM 起始)+------------------+ 0x08010000 (Flash 結束)
| .rodata | 只讀數據(Flash)
+------------------+
| .text | 代碼段(Flash)
+------------------+ 0x08000000 (Flash 起始)
各個區域用途:
存儲區域 | 用途 | 存儲位置 |
---|---|---|
.text(Code) | 存放編譯后的程序指令(代碼) | Flash(只讀) |
.rodata(RO-data) | 存放只讀常量(const ?變量等) | Flash(只讀) |
.data(RW-data) | 已初始化的全局/靜態變量 | Flash + RAM |
.bss(ZI-data) | 未初始化的全局/靜態變量,啟動時清零 | RAM(自動清零) |
Heap(堆) | malloc() ?動態分配的內存 | RAM(向上增長) |
Stack(棧) | 局部變量、函數調用返回地址 | RAM(向下增長) |
注意:
.data
?段在?Flash?里存有初值,但運行時會被復制到?RAM。.bss
?段不會占用 Flash,但會在 RAM 里初始化為 0。- 棧和堆共享 RAM,棧 Stack 從 高地址→?低地址方向增長。
- 堆 Heap 從 低地址→?高地址 方向增長。
- 當 Stack 和 Heap 彼此增長到一個臨界點(即二者相遇),就會導致 Stack Overflow(棧溢出)。
下面舉一個例子供大家更深入了解代碼變量的存儲方式:
int a = 0; //全局初始化區
char *p1; //全局未初始化區
main()
{ int b;//棧 char s[] = "abc";//棧 char * p2;//棧 char * p3 = "123456";//123456\0在常量區,p3在棧上。 static int c =0;//全局(靜態)初始化區 p1 = (char *)malloc(10);//堆 p2 = (char *)malloc(20);//堆
}
3.如何查看自己代碼的各個區的地址位置
3.1 為什么要查看各個區的地址
雖然我們在官方文檔中知道了Cortex-M3的各個部分的大概范圍,但我們使用CUBEMX或者操作系統生成的項目肯定不會規規矩矩的完全按照官方文檔的推薦來配置各個區。有時候推薦的去大小沒占完,你不知道結束地址。比如RT-Thread的內存管理將.data段和.bss段也一起管理起來了,創建各種任務所使用的內存都由RT-Thread統一分配。或者在CUBEMX中你可以自行設計堆區和棧區的大小等等。
而我們在使用內存管理的時候開辟一個專門存放變量的空間,我們需要的不只是簡單的知道這個變量的大小,我們還需要知道它的地址(要不然你使用這個變量,還要編譯器找這個地址在哪里,這樣才能提升項目的管理分配和運行效率,那根我直接開辟一個變量有什么區別?)而我開辟的地方也有講究,因為只有Heap(堆區)才能給與我們自由分配空間并進行讀寫的權利。因此找到各個區的地址我認為是有必要的。
那我們應該怎么找到各個區的地址呢?
那就是查看代碼編譯后生成的.map文件
3.2 解析查找地址的.map文件的代碼含義
代碼來源:https://www.waveshare.net/w/upload/3/39/E-Paper_code.7z
注意:你要使用了malloc等函數才會存在heap區的開辟,要不然你在.map文件中是找不到heap的創建地址的。
我們得到了下面的關于堆的說明
?HEAP? ? ? ? ? ? ? ? ? ? ?0x200002e0 ? Section ? ?49152 ?startup_stm32f103xe.o(HEAP)?
? ?
STACK? ? ? ? ? ? ? ? ? ??0x2000c2e0 ? Section ? ? 4096 ?startup_stm32f103xe.o(STACK)
__heap_base? ? ? ? ?0x200002e0 ? Data ? ? ? ? ? 0? ? ? ?startup_stm32f103xe.o(HEAP)
__heap_limit? ? ? ? ? 0x2000c2e0 ? Data ? ? ? ? ? 0? ? ? ?startup_stm32f103xe.o(HEAP)
__initial_sp? ? ? ? ? ? 0x2000d2e0 ? Data ? ? ? ? ? 0? ? ? ?startup_stm32f103xe.o(STACK)
HEAP:是堆的基地址
__initial_sp:是棧的起始地址(棧頂)。
__heap_base
:堆的??起始地址??。
__heap_limit
:堆的??結束地址??。
示意圖如下
4. 總結
如果我們設置了堆的空間大小,但是我們程序中沒有進行malloc申請,那么在程序事假運行的時候,我們棧的空間超過本身設置的空間,進入到堆里面,那么程序是不會出錯的,但是超過了堆的空間了,進入到全局變量區域,就會出現莫名其妙的錯誤。
不使用malloc,我們可以將堆設置成0,這是沒有問題的,但是棧的空間大小要設置成合適的,不然就會因為棧溢出,進入harderror,程序奔潰。