CODE,RO-DATA,RW-DATA,ZI-DATA+單片機程序執行流程+堆(heap)和棧(stack)==精華詳解

最近一直在研究單片機比較核心的東西,像題目中說的這些,於是在網絡上找了好多資料,看了好多博文、論壇,再配合自己的實驗,現在終於搞得差不多了,網上各位大俠的博文給了我不少幫助,在此先感謝各位大俠^_^


一.CODE,RO-DATA,RW-DATA,ZI-DATA

    CODE:顧名思義,就是代碼,指程序中代碼即函數體的大小,注意程序中未使用的函數也會算在CODE中,也即會佔用FLASH空間,因此不用的函數最好刪除掉,以免佔用過多FLASH空間;

    RO-DATA:RO就是隻讀的意思,程序中只讀的變量(也就是帶Const的)和已初始化的字符串等

    RW-DATA:特指已初始化的可讀可寫全局/靜態變量

    ZI-DATA:未初始化的可讀可寫全局/靜態變量,注意初始化爲0也算做未初始化

    CODE+RO-DATA+RW-DATA=程序大小=程序佔用的FLASH空間=BIN文件大小,爲什麼ZI-DATA不算?因爲對於未初始化的變量BIN文件裏面只需要兩句話描述一下就可以了,大概就是記錄下ZI-DATA的大小就好,程序運行時直接在RAM中開闢一個相應大小的空間;

    RW-DATA+ZI-DATA=程序所有全局變量的大小=程序固定佔用的RAM大小,我看的其中一篇博文中說RW-DATA+ZI-DATA就是程序總共會佔用的RAM大小,實則這只是程序固定佔用的RAM大小,非固定的是啥呢?比如說函數體裏面的局部變量,局部變量是會另外佔用空間的。


二.單片機程序執行流程

    1.在RAM中開闢一個RW-DATA大小的空間,並從FLASH中將對應的各個變量的值讀取入+賦值

    2.接着上一個開闢的空間的末尾再開闢一個ZI-DATA大小的空間,全部初始化爲0

    3.根據PC指針從“FLASH"中讀取CODE(指令),並執行,程序開始運轉

    注意:對於計算機來說CODE是會加載到內存中的,但對於單片機是在FLASH中的不佔用內存,這一點很好測試:你DEBUG一個程序,你會發現PC指針指向的是0x08000000以後的地址的(當你的BOOT設置是從FLASH啓動時);其實對於像STM32這種32位的單片機來說其內存最大大小爲4GB,flash的地址空間0x08000000~0x0807ffff,在4GB的範圍之內,所以STM32對flash的訪問也就相當於對其內存的訪問,只不過在硬件上這段地址對應的是可讀可寫斷電保存的flash;對寄存器的訪問同理。


三.堆和棧

    對於堆和棧,比較統一的說法是:堆是用來存放全局/靜態變量的,而棧是用來存放局部變量的;

    而根據測試結果來看,感覺堆是沒有什麼存在的意義的,因爲無論你堆取得大或者小,全局/靜態變量都是從內存首地址0x20000000開始佔位的,向下生長,甚至你取成0也是這樣,並不會影響程序執行;而棧的設置是有影響的,它影響的就是局部變量的首地址。

    值得一提的是,堆是向下(高地址)生長的,而棧是向上生長的,這樣若棧的空間設置的不夠大的話不是會倒置堆和棧重疊嗎(應該就是所謂的堆棧溢出)?的確是這樣,所以若要防止堆棧溢出,就要合理的設置棧的空間,如何設置纔算合理呢?我的想法是這樣的:分析程序,大概知道下程序最大可能會有多少Byte的局部變量存在,那麼這個就是你的棧的最小大小;然後計算出全局/靜態變量的大小,也就是RW-DATA+ZI-DATA,此即堆的大小,如果是12KB則表示堆空間的末地址在12KB處;最後棧的首地址就是堆的大小加上棧需要的空間大小。

    其實,簡單的測試下你會發現,即便把棧的首地址設置成0或者讓局部變量的大小明顯超過棧的大小程序很多時候也能正常運行(有些時候是會卡死的),那是因爲編譯器並不會完全按照你代碼裏的設置去設置棧的首地址,而是會結合實際代碼對棧空間的需求去設置,看到這肯定有人會問:既然這樣那還需要我們手動去設置棧的大小嗎?我的想法是這樣:對於簡單的程序編譯器應該能合理的設置好棧,但是當程序複雜的時候,涉及到的函數體、局部變量很多,函數體的循環嵌套調用很多的時候編譯器就很難正確的設置好棧了,畢竟棧的大小不是簡單的計算下所有局部變量的大小,而是要根據程序執行邏輯推理得出同一時間程序會佔用的最大內存空間,我覺得編譯器是很難做到這麼智能的,這個操作只能由熟悉此代碼的我們來操作。


四.測試方式/流程

    這裏纔是重點,前面說了很多但是並沒有把我所有測試所得都歸納完整,下面我把我部分測試過程寫出來,大家可以自行評估總結下:

    首先我們最好有一個還沒有任何全局/靜態變量的工程(stm32的),原以爲這個會比較難找,沒想到隨便找了個工程模板打開看了下居然就是還完全沒有全局/靜態變量的,所有C文件裏面都沒有定義任何全局變量,所有函數體裏面也沒有定義任何靜態變量,不知你們的工程模板是怎樣的,如果需要這麼個工程的話可以郵件:[email protected]

    1.定義一個全局變量,使用printf打印出它的內存地址,發現就是正正好的0x20000000,說明全局/靜態變量就是從內存首地址開始的

    2.再定義一個全局變量,將這兩個變量的地址都打印出來(都是無符號8位的變量),發現其地址就是0x20000001,說明堆是向下生長的

    3.定義一個只讀變量,打印出其地址,發現其地址是以0x08000000開頭的,也就是處於flash區域,說明RO-DATA的確不佔用RAM空間

    4.定義一個局部變量,打印出其地址,發現是以0x20000000開頭的,並且就在我們定義的棧首地址附近(之所以是附近而不是起始是因爲還存在其它局部變量),說明局部變量是處於棧區的,並且我們定義的棧首地址對實際的棧地址是有影響的

    5.定義一個靜態變量,打印出其地址,發現爲0x20000002,說明靜態變量是處於堆區的

    6.再定義一個局部變量,打印出其地址,發現其地址比之前定義的局部變量地址小,說明棧是向上生長的

    7.將棧的大小設置爲100字節,並定義一個200字節大小的數組(全局),打印出數組某些元素的地址(這邊打印地址並不是爲了看其地址,而是爲了在程序裏面有調用該數組,防止被編譯器忽略),發現程序能夠正常運行,並且棧首地址向後偏移了,說明編譯器自動爲我們設置好了棧首地址

    8.棧首地址不變,將數組大小設置爲5,再定義一個200字節大小的數組(局部),打印出某些元素的地址(也是爲了防止被編譯器忽略),發現程序在執行到該局部數組所在的函數體後卡死了(之前的能夠正常打印出來),說明在這種情況下編譯器並沒有合理設置好棧地址

    9.其他不變,將全局數組大小設置爲100,再編譯運行程序,發現程序能夠正常運行,說明在加大了全局變量數量後觸發了編譯器將棧首地址往後移

    10.設置讓編譯器生成bin文件,然後編譯,將CODE,RO-DATA,RW-DATA相加,發現其大小正好與bin文件的大小一模一樣,一字節不差

    11.將Heap_size設置爲0,編譯運行,發現程序能夠正常運行,說明堆的大小並不影響程序運行

    12.定義一個函數,函數裏面的內容任意,但是程序裏面並不調用該函數,編譯發現CODE變大了,說明未使用的函數也是會佔用flash空間的


    以上若有說得不對的地方,歡迎指正!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章