STM32 大小端模式 與 堆疊及其增長方向分析

十日十乞001發表於2017-06-26

在開源電子中看到一篇文章講的是棧增長和大端/小端問題。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 部分有:
==============================================================================
Image component sizes

      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Object Name
       172         10          0          4          0        995   delay.o//delay.c裡面,fac_us和fac_ms,共佔用4位元組
       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位元組用於對其
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Member Name
         8          0          0          0          0         68   __main.o
       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個位元組
    ----------------------------------------------------------------------
      Code (inc. data)   RO Data    RW Data    ZI Data      Debug   Library Name
       762         30         40          0         96       1164   c_w.l
      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   Grand Totals
      3918        404        378         20       2348     217111   ELF Image Totals
      3918        404        378         20          0          0   ROM Totals
==============================================================================
    Total RO  Size (Code + RO Data)                 4296 (   4.20kB)
    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;
//CPU大小端
//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;  
 }  
}

網友:
有人提出全域性變數和靜態變數是儲存在靜態區而不是堆中,並且編寫了一個測試程式碼,(原帖48樓)修改了堆和棧的長度,main函式增加了初始化串列埠緩衝的操作,和呼叫了一個無限迭代的函式.堆疊位置在<test.map>line:431-435和line:779-780.F11執行函式Iteration,會看到記憶體區會不斷被Iteration的地址覆蓋,直到破壞所有靜態變數空間.如果靜態變數在棧區,按照棧先進後出的機制,應該不會被破壞.當然,如果連結器是先放棧,再放堆,最後放靜態變數,就做不了這個實驗了.後來原子哥再三實驗,也認可了這種說法,

原子哥做的修改:

一、記憶體基本構成  
可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。  

靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量。  

棧區:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。  

堆區:亦稱動態記憶體分配。程式在執行的時候用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,則沒有該區),之後是棧區。

相關文章