STM32 大小端模式 與 堆疊及其增長方向分析
在開源電子中看到一篇文章講的是棧增長和大端/小端問題。學C語言的時候,我們知道堆疊的區別:
1)棧區(stack):由編譯器自動分配和釋放,存放函式的引數值、區域性變數的值等,其操作方式類似
於資料結構中的棧。
(2)堆區(heap):一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時可能由作業系統回收。分配
方式類似於資料結構中的連結串列。
(3)全域性區(靜態區)(static):全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態
變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系
統自動釋放。
(4)文字常量區:常量字串就是存放在這裡的。
(5)程式程式碼區:存放函式體的二進位制程式碼。
下面是原子哥的帖子:主要意思是要證明stm32是小端模式,堆從RAM的起始地址處(0x2000 0000)分配記憶體給全域性變數和靜態變數,並且堆是向上增長,棧是向下增長。
在後續的討論中有人認為全域性變數和靜態變數是放在靜態區的,在編譯時分配記憶體,靜態區從RAM的起始地址分配記憶體,並且是連續的。接著才是堆,堆上的記憶體是動態分配的,由malloc或者new申請。棧上存放區域性變數,形參等。
在經過再三實驗, 原子哥最終認同了這種觀點,做了相應修改。
與之相關的討論有:
http://www.openedv.com/thread-24152-1-1.html;http://blog.csdn.NET/slj_win/article/details/16906141 ;http://www.openedv.com/posts/list/26805.htm;
http://www.openedv.com/posts/list/26805.htm
原子哥:
1,首先來看:棧(STACK)的問題.函式的區域性變數,都是存放在"棧"裡面,棧的英文是:STACK.STACK的大小,我們可以在stm32的啟動檔案裡面設定,以戰艦stm32開發板為例,在startup_stm32f10x_hd.s裡面,開頭就有:
Stack_Size EQU 0x00000800
表示棧大小是0X800,也就是2048位元組.這樣,CPU處理任務的時候,函式區域性變數做多可佔用的大小就是:2048位元組,注意:是所有在處理的函式,包括函式巢狀,遞迴,等等,都是從這個"棧"裡面,來分配的.所以,如果一個函式的區域性變數過多,比如在函式裡面定義一個u8 buf[512],這一下就佔了1/4的棧大小了,再在其他函式裡面來搞兩下,程式崩潰是很容易的事情,這時候,一般你會進入到hardfault....
這是初學者非常容易犯的一個錯誤.切記不要在函式裡面放N多區域性變數,尤其有大陣列的時候!
對於棧區,一般棧頂,也就是MSP,在程式剛執行的時候,指向程式所佔用記憶體的最高地址.比如附件裡面的這個程式序,記憶體佔用如下圖:
圖中,我們可以看到,程式總共佔用記憶體:20+2348位元組=2368=0X940
那麼程式剛開始執行的時候:MSP=0X2000 0000+0X940=0X2000 0940.
事實上,也是如此,如圖:
圖中,MSP就是:0X2000 0940.
程式執行後,MSP就是從這個地址開始,往下給函式的區域性變數分配地址.
再說說棧的增長方向,我們可以用如下程式碼測試:
//儲存棧增長方向
//0,向下增長;1,向上增長.
static u8 stack_dir;
//查詢棧增長方向,結果儲存在stack_dir裡面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用於存放第一個dummy的地址。
u8 dummy; //用於獲取棧地址
if(addr==NULL) //第一次進入
{
addr=&dummy; //儲存dummy的地址
find_stack_direction (); //遞迴
}else //第二次進入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大於第一次dummy,那麼說明棧增長方向是向上的.
else stack_dir=0; //第二次dummy的地址小於第一次dummy,那麼說明棧增長方向是向下的.
}
}
這個程式碼不是我寫的,網上抄來的,思路很巧妙,利用遞迴,判斷兩次分配給dummy的地址,來比較棧是向下生長,還是向上生長.
如果你在STM32測試這個函式,你會發現,STM32的棧,是向下生長的.事實上,一般CPU的棧增長方向,都是向下的.
2,再來說說,堆(HEAP)的問題.
全域性變數,靜態變數,以及記憶體管理所用的記憶體,都是屬於"堆"區,英文名:"HEAP"
與棧區不同,堆區,則從記憶體區域的起始地址,開始分配給各個全域性變數和靜態變數.
堆的生長方向,都是向上的.在程式裡面,所有的記憶體分為:堆+棧. 只是他們各自的起始地址和增長方向不同,他們沒有一個固定的界限,所以一旦堆疊衝突,系統就到了崩潰的時候了.
同樣,我們用附件裡面的例程測試:
stack_dir的地址是0X20000004,也就是STM32的記憶體起始端的地址.
這裡本來應該是從0X2000 0000開始分配的,但是,我模擬發現0X2000 0000總是存放:0X2000 0398,這個值,貌似是MSP,但是又不變化,還請高手幫忙解釋下.
其他的,全域性變數,則依次遞增,地址肯定大於0X20000004,比如cpu_endian的地址就是0X20000005.
這就是STM32內部堆的分配規則.
3,再說說,大小端的問題.
大端模式:低位位元組存在高地址上,高位位元組存在低地址上
小端模式:高位位元組存在高地址上,低位位元組存在低地址上
STM32屬於小端模式,簡單的說,比如u32 temp=0X12345678;
假設temp地址在0X2000 0010.
那麼在記憶體裡面,存放就變成了:
地址 | HEX |
0X2000 0010 | 78 56 43 12 |
CPU到底是大端還是小端,可以通過如下程式碼測試:
//CPU大小端
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//獲取CPU大小端模式,結果儲存在cpu_endian裡面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
以上測試,在STM32上,你會得到cpu_endian=0,也就是小端模式.
3,最後說說,STM32記憶體的問題.
還是以附件工程為例,在前面第一個圖,程式總共佔用記憶體:20+2348位元組,這麼多記憶體,到底是怎麼得來的呢?
我們可以雙擊Project側邊欄的:Targt1,會彈出test.map,在這個裡面,我們就可以清楚的知道這些記憶體到底是怎麼來的了.在這個test.map最後,Image 部分有:
==============================================================================
Code (inc. data) RO Data RW Data ZI Data Debug Object Name
112 12 0 0 0 427 led.o
72 26 304 0 2048 828 startup_stm32f10x_hd.o //啟動檔案,裡面定義了Stack_Size為0X800,所以這裡是2048.
712 52 0 0 0 2715 sys.o
348 154 0 6 0 208720 test.o//test.c裡面,stack_dir和cpu_endian 以及*addr ,佔用6位元組.
384 24 0 8 200 3050 usart.o//usart.c定義了一個串列埠接收陣列buffer,佔用200位元組.
1800 278 336 20 2248 216735 Object Totals //總共2248+20位元組
0 0 32 0 0 0 (incl. Generated)
0 0 0 2 0 0 (incl. Padding)//2位元組用於對其
104 0 0 0 0 84 __printf.o
52 8 0 0 0 0 __scatter.o
26 0 0 0 0 0 __scatter_copy.o
28 0 0 0 0 0 __scatter_zi.o
48 6 0 0 0 96 _printf_char_common.o
36 4 0 0 0 80 _printf_char_file.o
92 4 40 0 0 88 _printf_hex_int.o
184 0 0 0 0 88 _printf_intcommon.o
0 0 0 0 0 0 _printf_percent.o
4 0 0 0 0 0 _printf_percent_end.o
6 0 0 0 0 0 _printf_x.o
12 0 0 0 0 72 exit.o
8 0 0 0 0 68 ferror.o
6 0 0 0 0 152 heapauxi.o
2 0 0 0 0 0 libinit.o
2 0 0 0 0 0 libinit2.o
2 0 0 0 0 0 libshutdown.o
2 0 0 0 0 0 libshutdown2.o
8 4 0 0 96 68 libspace.o //庫檔案(printf使用),佔用了96位元組
24 4 0 0 0 84 noretval__2printf.o
0 0 0 0 0 0 rtentry.o
12 0 0 0 0 0 rtentry2.o
6 0 0 0 0 0 rtentry4.o
2 0 0 0 0 0 rtexit.o
10 0 0 0 0 0 rtexit2.o
74 0 0 0 0 80 sys_stackheap_outer.o
2 0 0 0 0 68 use_no_semi.o
2 0 0 0 0 68 use_no_semi_2.o
450 8 0 0 0 236 faddsub_clz.o
388 76 0 0 0 96 fdiv.o
62 4 0 0 0 84 ffixu.o
38 0 0 0 0 68 fflt_clz.o
258 4 0 0 0 84 fmul.o
140 4 0 0 0 84 fnaninf.o
10 0 0 0 0 68 fretinf.o
0 0 0 0 0 0 usenofp.o
2118 126 42 0 100 1884 Library Totals //呼叫的庫用了100位元組.
10 0 2 0 4 0 (incl. Padding) //用於對其多佔用了4個位元組
1346 96 0 0 0 720 fz_ws.l
2118 126 42 0 100 1884 Library Totals
Code (inc. data) RO Data RW Data ZI Data Debug
3918 404 378 20 2348 217111 ELF Image Totals
3918 404 378 20 0 0 ROM Totals
Total RW Size (RW Data + ZI Data) 2368 ( 2.31kB) //總共佔用:2248+20+100=2368.
Total ROM Size (Code + RO Data + RW Data) 4316 ( 4.21kB)
通過這個檔案,我們就可以分析整個記憶體,是怎麼被佔用的,具體到每個檔案,佔用多少.一目瞭然了.
4,最後,看看整個測試程式碼:
main.c程式碼如下,工程見附件.
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
//ALIENTEK戰艦STM32開發板堆疊增長方向以及CPU大小端測試
//0,向下增長;1,向上增長.
static u8 stack_dir;
//0,小端模式;1,大端模式.
static u8 cpu_endian;
//查詢棧增長方向,結果儲存在stack_dir裡面.
void find_stack_direction(void)
{
static u8 *addr=NULL; //用於存放第一個dummy的地址。
u8 dummy; //用於獲取棧地址
if(addr==NULL) //第一次進入
{
addr=&dummy; //儲存dummy的地址
find_stack_direction (); //遞迴
}else //第二次進入
{
if(&dummy>addr)stack_dir=1; //第二次dummy的地址大於第一次dummy,那麼說明棧增長方向是向上的.
else stack_dir=0; //第二次dummy的地址小於第一次dummy,那麼說明棧增長方向是向下的.
}
}
//獲取CPU大小端模式,結果儲存在cpu_endian裡面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
int main(void)
{
Stm32_Clock_Init(9); //系統時鐘設定
uart_init(72,9600); //串列埠初始化為9600
delay_init(72); //延時初始化
LED_Init(); //初始化與LED連線的硬體介面
printf("stack_dir:%x\r\n",&stack_dir);
printf("cpu_endian:%x\r\n",&cpu_endian);
find_stack_direction(); //獲取棧增長方式
find_cpu_endian(); //獲取CPU大小端模式
while(1)
{
if(stack_dir)printf("STACK DIRCTION:向上生長\r\n\r\n");
else printf("STACK DIRCTION:向下生長\r\n\r\n");
if(cpu_endian)printf("CPU ENDIAN:大端模式\r\n\r\n");
else printf("CPU ENDIAN:小端模式\r\n\r\n");
delay_ms(500);
LED0=!LED0;
}
}
一、記憶體基本構成
可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。
靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量。
棧區:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
堆區:亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意大小的記憶體,程式設計師自己負責在適當的時候用free或delete釋放記憶體。動態記憶體的生存期可以由我們決定,如果我們不釋放記憶體,程式將在最後才釋放掉動態記憶體。 但是,良好的程式設計習慣是:如果某動態記憶體不再使用,需要將其釋放掉,否則,我們認為發生了記憶體洩漏現象。
按照這個說法,我在.s檔案裡面設定了:
Heap_Size EQU 0x00000000
也就是,沒有任何動態記憶體分配。
這樣,記憶體=靜態儲存區+棧區了。
不存在堆!!!
因為我沒有用malloc來動態分配記憶體。
因此,前面提到的一切堆區,其實就是靜態儲存區。
一、記憶體基本構成
可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。
靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量。
棧區:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
堆區:亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意大小的記憶體,程式設計師自己負責在適當的時候用free或delete釋放記憶體。動態記憶體的生存期可以由我們決定,如果我們不釋放記憶體,程式將在最後才釋放掉動態記憶體。 但是,良好的程式設計習慣是:如果某動態記憶體不再使用,需要將其釋放掉,否則,我們認為發生了記憶體洩漏現象。
按照這個說法,我在.s檔案裡面設定了:
Heap_Size EQU 0x00000000
也就是,沒有任何動態記憶體分配。
這樣,記憶體=靜態儲存區+棧區了。
不存在堆!!!
因為我沒有用malloc來動態分配記憶體。
因此,前面提到的一切堆區,其實就是靜態儲存區。
另外,經過測試,確實是這樣。
STM32的記憶體分配,應該分為兩種情況。
1,使用了系統的malloc。
2,未使用系統的malloc。
第一種情況(使用malloc):
STM32的記憶體分配規律:
從0X20000000開始依次為:靜態儲存區+堆區+棧區
第二種情況(不使用malloc):
STM32的記憶體分配規律:
從0X20000000開始依次為:靜態儲存區+棧區
第二種情況不存在堆區。
所以,一般對於我們開發板例程,實際上,沒有所謂堆區的概念,而僅僅是:靜態儲存區+棧區。
無論哪種情況,所有的全域性變數,包括靜態變數之類的,全部儲存在靜態儲存區。
緊跟靜態儲存區之後的,是堆區(如沒用到malloc,則沒有該區),之後是棧區。
相關文章
- 棧與堆的區別以及增長方向
- junkman 遠端堆疊監控
- JS 堆疊JS
- java堆疊Java
- 堆疊圖
- 平衡堆疊
- 大小端儲存模式模式
- tomcat服務無響應堆疊分析Tomcat
- ThreadDump分析筆記(一) 解讀堆疊thread筆記
- Python實現堆疊與佇列Python佇列
- mplus資料分析:增長模型潛增長模型與增長混合模型再解釋模型
- Java多執行緒-程式執行堆疊分析Java執行緒
- JVM 執行緒堆疊分析過程詳解JVM執行緒
- 圖的深度優先遍歷[非堆疊、堆疊實現]
- 記憶體堆疊記憶體
- 堆疊的工作原理
- C#堆疊(Stack)C#
- Java之String和StringBuffer堆疊圖分析Java
- Java堆疊的深度分析及記憶體管理技巧Java記憶體
- Android學習之 Activity堆疊管理與控制Android
- C#中堆和堆疊的區別C#
- 單條記錄大小增長倍數和ibd檔案大小的增長倍數不成正比
- [golang]如何看懂呼叫堆疊Golang
- 華為裝置堆疊原理
- Thrift的網路堆疊
- C++堆疊詳解C++
- 泛型鏈式堆疊泛型
- 第六講 堆疊操作
- 益智補劑:Stamets堆疊
- 分享一款JVM執行緒堆疊線上分析工具JVM執行緒
- 圖的深度優先遍歷(堆疊實現和非堆疊實現)
- 「前端」History API與瀏覽器歷史堆疊管理前端API瀏覽器
- 有趣的CSS題目(3): 層疊順序與堆疊上下文知多少CSS
- 什麼是網路堆疊?
- Java 堆疊記憶體分配Java記憶體
- iOS crash 日誌堆疊解析iOS
- (js佇列,堆疊) (FIFO,LIFO)JS佇列
- z-index堆疊規則Index