在討論Cortex-M的內(nèi)存之前,先來看看Cortex-M的存儲器系統(tǒng),我們知道,Cortex-M系列的處理器,大都可以對32的存儲器進(jìn)行尋址,因此存儲器的尋址空間能夠達(dá)到4G,這就意味著指定和數(shù)據(jù)共用相同的地址空間,也就是將程序存儲器、數(shù)據(jù)存儲器、寄存器和輸入輸出端口被組織在同一個(gè)4GB的線性地址空間內(nèi)。數(shù)據(jù)字節(jié)以小端格式存放在存儲器中。一個(gè)字里的最低地址字節(jié)被認(rèn)為是該字的最低有效字節(jié),而最高地址字節(jié)是最高有效字節(jié)。
1 Cortex-M存儲器架構(gòu)
4G的地址空間就是地址編碼的范圍。所謂編碼就是對每一個(gè)程序存儲器、數(shù)據(jù)存儲器、寄存器和輸入輸出端口(一個(gè)字節(jié))分配一個(gè)唯一的地址號碼,這個(gè)過程又叫做“編址”或者“地址映射”。這個(gè)過程就好像在日常生活中我們給每家每戶分配一個(gè)地址門牌號。與編碼相對應(yīng)的是“尋址”過程——分配一個(gè)地址號碼給一個(gè)存儲單元的目的是為了便于找到它,完成數(shù)據(jù)的讀寫,這就是“尋址”,因此地址空間有時(shí)候又被稱作“尋址空間”。
有了4G的可尋址空間,我們就可通過尋址來操作相應(yīng)的地址對象。這就需要將程序存儲器、數(shù)據(jù)存儲器、寄存器和輸入輸出端口進(jìn)行統(tǒng)一編號,也就是存儲器映射。
存儲器映射是指把芯片中或芯片外的FLASH,RAM,外設(shè),BOOTBLOCK等進(jìn)行統(tǒng)一編址。即用地址來表示對象。這個(gè)地址絕大多數(shù)是由廠家規(guī)定好的,用戶只能用而不能改。用戶只能在掛外部RAM或FLASH的情況下可進(jìn)行自定義。
如下圖,是Cortex-M3存儲器映射結(jié)構(gòu)圖。
Cortex-M3是32位的內(nèi)核,因此其PC指針可以指向2^32=4G的地址空間,也就是0x0000_0000——0xFFFF_FFFF這一大塊空間。根據(jù)圖中描述,Cortex-M3內(nèi)核將0x0000_0000——0xFFFF_FFFF這塊4G大小的空間分成8大塊:代碼、SRAM、外設(shè)、外部RAM、外部設(shè)備、專用外設(shè)總線-內(nèi)部、專用外設(shè)總線-外部、特定廠商等,因此使用該內(nèi)核的設(shè)計(jì)者必須按照這個(gè)進(jìn)行各自芯片的存儲器結(jié)構(gòu)設(shè)計(jì)。
首先,我們對比一下Cortex-M3存儲器結(jié)構(gòu)和STM32存儲器結(jié)構(gòu):
圖中可以很清晰的看到,STM32的存儲器結(jié)構(gòu)和Cortex-M3的很相似,不同的是,STM32加入了很多實(shí)際的東西,如:Flash、SRAM等。只有加入了這些東西,才能成為一個(gè)擁有實(shí)際意義的、可以工作的處理芯片——STM32。
STM32的存儲器地址空間被劃分為大小相等的8塊區(qū)域,每塊區(qū)域大小為512MB。
地址范圍 | 描述 |
---|---|
0x0000 0000 ~0x2000 0000 | 根據(jù)啟動引腳的狀態(tài)決定哪個(gè)存儲空間被映射到此處。 片內(nèi)系統(tǒng)存儲區(qū)起始地址:0x1fff0000(2K字節(jié)的空間) |
0x2000 0000 ~0x4000 0000 | SRAM區(qū),64K,其中位帶別名區(qū)首地址為:0x2200 0000 |
0x4000 0000 ~0x6000 0000 | 用于片內(nèi)外設(shè),外設(shè)寄存器的別名區(qū)首地址:0x4200 0000 |
0x6000 0000 ~0x8000 0000 | |
0x8000 0000 ~0xa000 0000 | 片上flash存儲區(qū)512M |
0xa000 0000 ~0xc000 0000 | |
0xc000 0000 ~0xe000 0000 | |
0xe000 0000 ~0xffff ffff |
對STM32存儲器知識的掌握,實(shí)際上就是對Flash和SRAM這兩個(gè)區(qū)域知識的掌握。由STM32的系統(tǒng)結(jié)構(gòu)可以看出,F(xiàn)lash和SRAM這兩個(gè)區(qū)域分別由ICode總線和DCode總線與處理器通信,以此完成相應(yīng)的數(shù)據(jù)交換。
當(dāng)然啦,其他Cortex-M的處理和STM32的也是類似的,比如GD32、CH32等。
下面將重點(diǎn)描述Flash和SRAM的知識。
1.2 Cortex-M的SRAM
RAM隨機(jī)存儲器(Random Access Memory)表示既可以從中讀取數(shù)據(jù),也可以寫入數(shù)據(jù)。當(dāng)機(jī)器電源關(guān)閉時(shí),存于其中的數(shù)據(jù)就會丟失。比如電腦的內(nèi)存條。
RAM有兩大類,一種稱為靜態(tài)RAM(Static RAM/SRAM),SRAM速度非常快,是目前讀寫最快的存儲設(shè)備了,但是它也非常昂貴,所以只在要求很苛刻的地方使用,譬如CPU的一級緩沖,二級緩沖。另一種稱為動態(tài)RAM(Dynamic RAM/DRAM),DRAM保留數(shù)據(jù)的時(shí)間很短,速度也比SRAM慢,不過它還是比任何的ROM都要快,但從價(jià)格上來說DRAM相比SRAM要便宜很多,計(jì)算機(jī)內(nèi)存就是DRAM的。
DRAM分為很多種,常見的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,這里介紹其中的一種DDR RAM。
DDR RAM(Date-Rate RAM)也稱作DDR SDRAM,這種改進(jìn)型的RAM和SDRAM是基本一樣的,不同之處在于它可以在一個(gè)時(shí)鐘讀寫兩次數(shù)據(jù),這樣就使得數(shù)據(jù)傳輸速度加倍了。這是目前電腦中用得最多的內(nèi)存,而且它有著成本優(yōu)勢,事實(shí)上擊敗了Intel的另外一種內(nèi)存標(biāo)準(zhǔn)-Rambus DRAM。在很多高端的顯卡上,也配備了高速DDR RAM來提高帶寬,這可以大幅度提高3D加速卡的像素渲染能力。
為什么需要RAM,因?yàn)橄鄬lASH而言,RAM的速度快很多,所有數(shù)據(jù)在FLASH里面讀取太慢了,為了加快速度,就把一些需要和CPU交換的數(shù)據(jù)讀到RAM里來執(zhí)行。
STM32單片機(jī)內(nèi)部的 RAM 為 SRAM。不同類型的Cortex-M單片機(jī)的SRAM大小是不一樣的,但起始地址都是0x2000 0000,終止地址都是0x2000 0000+其固定的容量大小。SRAM相對容量小,速度快,掉電數(shù)據(jù)丟失,其作用是用來存取各種動態(tài)的輸入輸出數(shù)據(jù)、中間計(jì)算結(jié)果以及與外部存儲器交換的數(shù)據(jù)和暫存數(shù)據(jù)。設(shè)備斷電后,SRAM中存儲的數(shù)據(jù)就會丟失。
1.3 Cortex-M的Flash
Cortex-M的Flash,嚴(yán)格說,應(yīng)該是Flash模塊。該Flash模塊包括: Flash主存儲區(qū)(Main memory)、Flash信息區(qū)(Information block),以及Flash存儲接口寄存器區(qū)(Flash memory interface) 。三個(gè)組成部分分別在0x0000 0000——0xFFFF FFFF不同的區(qū)域。下面介紹STM32的Flash,如下表所示。
STM32的閃存模塊由:__主存儲器、信息塊和閃存儲器塊__3部分組成。
主存儲器 ,該部分用來存放代碼和數(shù)據(jù)常數(shù)(如加const類型的數(shù)據(jù))。對于大容量產(chǎn)品,其被劃分為256頁,每頁2K,注意,小容量和中容量產(chǎn)品則每頁只有1K字節(jié)。主存儲起的起始地址為0X08000000,B0、B1都接GND的時(shí)候,就從0X08000000開始運(yùn)行代碼。
信息塊 ,該部分分為2個(gè)部分,其中啟動程序代碼,是用來存儲ST自帶的啟動程序,用于串口下載,當(dāng)B0接3.3V,B1接GND時(shí),運(yùn)行的就這部分代碼,用戶選擇字節(jié),則一般用于配置保護(hù)等功能。
閃存儲器塊 ,該部分用于控制閃存儲器讀取等,是整個(gè)閃存儲器的控制機(jī)構(gòu)。
對于主存儲器和信息塊的寫入有內(nèi)嵌的閃存編程管理;編程與擦除的高壓由內(nèi)部產(chǎn)生。
在執(zhí)行閃存寫操作時(shí),任何對閃存的讀操作都會鎖定總線,在寫完成后才能正確進(jìn)行,在進(jìn)行讀取或擦除操作時(shí),不能進(jìn)行代碼或者數(shù)據(jù)的讀取操作。
2 C程序內(nèi)存分析
在C/C++程序中,編譯的程序占用內(nèi)存分為5個(gè)區(qū),分別為__棧區(qū)、堆區(qū)、全局/靜態(tài)存儲區(qū)、常量存儲區(qū)、代碼區(qū)__。
1.Text段(Code Segment/Text Segment,代碼段) :通常是指用來存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域,也就是存放CPU執(zhí)行的機(jī)器指令(machine instructions)。這部分區(qū)域的大小在程序運(yùn)行前就已經(jīng)確定,并且內(nèi)存區(qū)域通常屬于只讀(某些架構(gòu)也允許代碼段為可寫,即允許修改程序)。在代碼段中,也有可能包含一些只讀的常數(shù)變量,例如字符串常量等。
2.全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(Initialized data segment/Data segment) :該區(qū)包含了在程序中明確被初始化的全局變量、靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和常量數(shù)據(jù)(如字符串常量)。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配。static聲明的變量放在data段。
3.BSS段(Block Started by Symbol) :BSS段通常是指用來存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域。BSS段屬于靜態(tài)內(nèi)存分配。
4.堆(heap) :堆是用于存放程序運(yùn)行中被動態(tài)分配的內(nèi)存段,它的大小并不固定,可動態(tài)擴(kuò)張或縮減。也就是常說的用malloc,calloc, realloc 等函數(shù)分配的變量空間是在堆上。當(dāng)程序調(diào)用malloc等函數(shù)分配內(nèi)存時(shí),新分配的內(nèi)存就被動態(tài)添加到堆上(堆被擴(kuò)張);當(dāng)利用free等函數(shù)釋放內(nèi)存時(shí),被釋放的內(nèi)存從堆中被剔除(堆被縮減)。
5.棧(stack) :棧又稱堆棧,是用戶存放程序臨時(shí)創(chuàng)建的局部變量,也就是說我們函數(shù)括弧“{}”中定義的變量(但不包括static聲明的變量,static意味著在數(shù)據(jù)段中存放變量)。除此以外,在函數(shù)被調(diào)用時(shí),其參數(shù)也會被壓入發(fā)起調(diào)用的進(jìn)程棧中,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會被存放回棧中。由于棧的先進(jìn)先出(FIFO)特點(diǎn),所以棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場。從這個(gè)意義上講,我們可以把堆棧看成一個(gè)寄存、交換臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)。
一個(gè)程序本質(zhì)上都是由 __bss段、data段、text段__三個(gè)組成的。
在C/C++程序編譯完成之后,已初始化的全局變量保存在data 段中,未初始化的全局變量保存在bss 段中。
text和data段都在可執(zhí)行文件中(在嵌入式系統(tǒng)里一般是固化在鏡像文件中),由系統(tǒng)從可執(zhí)行文件中加載;而bss段不在可執(zhí)行文件中,由系統(tǒng)初始化。
3 STM32程序的存儲分配
3.1 程序所占RAM和Flash大小分析
為例調(diào)試方便,這里使用一個(gè)裸機(jī)串口例子,關(guān)于串口的使用請參看筆者博文:
串口通信:https://bruceou.blog.csdn.net/article/details/79341769
使用Keil編譯代碼,編譯信息如下:
其中:
- Code 代表執(zhí)行的代碼,程序中所有的函數(shù)都位于此處。即上述的text段。
- RO-data (Read Only)代表只讀數(shù)據(jù),程序中所定義的全局常量數(shù)據(jù)和字符串都位于此處,如const型。
- RW-data (Read Write) 代表已初始化的讀寫數(shù)據(jù),程序中定義并且初始化的全局變量和靜態(tài)變量位于此處。
- ZI-data (Zero Initialize) 代表未初始化的讀寫數(shù)據(jù),程序中定義了但沒有初始化的全局變量和靜態(tài)變量位于此處。Keil編譯器默認(rèn)是把你沒有初始化的變量都賦值為例0。即上述的bss段。
值得注意的是,這些參數(shù)的單位是Byte。
Code和RO-Data兩個(gè)段統(tǒng)稱為RO段,它們和RW段,需要燒錄到FLASH等非易失性器件中。
RW段需要燒錄到FLASH中,而ZI段則不用,但在運(yùn)行時(shí),它們都必須裝載到可讀可寫的RAM中。
因此我們可以計(jì)算出FLASH和RAM的大小:
Flash = Code + RO Data + RW Data
RAM = RW-data + ZI-data
這就要涉及到程序的兩種狀態(tài):加載域和運(yùn)行域。
加載域 :向Flash中下載程序時(shí),其實(shí)僅僅下載的是CODE+RO-data+RW-data的內(nèi)容,意思就是說,在掉電情況下,F(xiàn)lash里面的內(nèi)存僅包含CODE+RO-data+RW-data這三塊。
運(yùn)行域 :當(dāng)上電后,程序運(yùn)行時(shí),首先程序會從特定的地址進(jìn)行啟動,啟動時(shí)會將RW-data的數(shù)據(jù)加載到SRAM中,單片機(jī)的 RO區(qū)域不需要加載到 SRAM,內(nèi)核直接從 FLASH 讀取指令運(yùn)行。那ZI-data的數(shù)據(jù)怎么辦呢?對于初始值為0全局變量來說,因?yàn)橐贑ode區(qū)要調(diào)用該全局變量,所以肯定要對其進(jìn)行描述,程序運(yùn)行時(shí)就知道了,原來你是初始值為0的全局變量呀,然后就在SRAM區(qū)給你分配了一段固定區(qū)域的地址;對于局部變量來說,會自動分配大小。ZI-data有統(tǒng)計(jì)作用,并且SRAM中一段特定的區(qū)域是運(yùn)行ZI-data數(shù)據(jù),RW-DATA+ZI-DATA就是程序運(yùn)行總共會占用SRAM的長度,生成局部變量的棧空間包含在ZI-data區(qū)的范圍。
3.2 MAP 文件剖析
程序后成功編譯后,通過編譯信息可以查看程序空間分配情況,而map文件更加詳細(xì)的描述了程序編譯信息。
map文件是程序的全局符號、源文件和代碼行號信息的唯一的文本表示方法,它可以在任何地方、任何時(shí)候使用,不需要有額外的程序進(jìn)行支持。
在MDK5中,在項(xiàng)目中雙擊Target就能自動打開.map文件。
3.2.1 Section Cross References
該部分主要是不同文件中函數(shù)的調(diào)用關(guān)系。
這個(gè)以main.c中的main()函數(shù)為例,調(diào)用了stm32f1_bsp_led.c中的BSP_LED_Init()函數(shù),其他函數(shù)也都列出來了。
3.2.2 Removing Unused input sections
MDK優(yōu)化會刪除的冗余的函數(shù)。
以stm32f10x_gpio.c文件為例,很多函數(shù)沒有用到,因此這里就會時(shí)刪除冗余的函數(shù),減少代碼空間。
當(dāng)然啦,這部分和編譯器相關(guān),不一定會刪除,這里想要最大可能刪除冗余函數(shù),需要都選相應(yīng)的選項(xiàng),如下圖所示。
在 Removing Unused input sections from the image 的最后會列出刪除的冗余函數(shù)的大小,如果在MDK上改變上圖所示的配置,下圖中的刪除總代碼會有變化。
3.2.3 Image Symbol Table
Symbol Table會有兩個(gè)部分:Local Symbols和Global Symbols。
Local Symbols
該部分是Static聲明的全局變量以及C文件中函數(shù)的地址和static聲明的函數(shù)。
Global Symbols
該部分是全局變量以及C文件中函數(shù)。
3.2.4 Memory Map of the image
映像文件可以分為加載域(Load Region)和運(yùn)行域(Execution Region):加載域反映了ARM可執(zhí)行映像文件各個(gè)段存放在存儲器中時(shí)的位置關(guān)系。關(guān)于加載域和運(yùn)行域前文已經(jīng)介紹過了。
這部分為兩塊,一部分是Flash的,另外一部分是RAM的。
Flash中存放的是text段,代碼段不用加載到RAM中執(zhí)行,程序在運(yùn)行時(shí)MCU會直接從Flash中讀取指令。
第842行:Flash加載域的基本信息,這里size表示Code + RO Data + RW Data的大小,也就是文件后面ROM Size。
第844行:Flash運(yùn)行域的基本信息,這里size表示Code + RO Data的大小,也就是文件后面RO Size。運(yùn)行域的大小比加載域少了RW Data部分,這部分會在運(yùn)行時(shí)候加載到RAM中。
另外還可以看到Flash的大小時(shí)512 Kb。
RAM中存放的是data段和bss段,該部分是需要從Flash中加載進(jìn)來。
這里只有運(yùn)行域,這里的size包含RW Data + ZI Data,也就是RW Size,RAM的大小是64 Kb。該部分的最后兩行就是堆棧大小。
3.2.5 Image component sizes
這部分就是各個(gè)文件中各個(gè)數(shù)據(jù)段的大小。
在xxx.map文件的最后也會有不同數(shù)據(jù)段的信息統(tǒng)計(jì)。
3.3 程序堆棧使用分析
我們知道,程序運(yùn)行需要占用的大小是RAM = RW-data + ZI-data,而堆棧的大小是程序開始運(yùn)行后才能確定的,堆棧的內(nèi)存占用就是在上面RAM分配給RW-data + ZI-data之后的地址開始分配的。
那么堆和棧到底能占用多大呢,堆棧的大小是在startup_stm32fxxx.s中設(shè)置的,這里以STM32F103ZET6為例進(jìn)行分析,其內(nèi)部棧的大小為1KB,堆的大小為0.5KB。
startup_stm32fxxx.s文件是系統(tǒng)的啟動文件,主要包括堆和棧的初始化配置、中斷向量表的配置以及將程序引導(dǎo)到main( )函數(shù)等。
startup_stm32fxxx.s主要完成三個(gè)工作:棧和堆的初始化、定位中斷向量表、調(diào)用Reset Handler。
避免產(chǎn)生這類錯(cuò)誤的產(chǎn)生,程序設(shè)計(jì)時(shí)就應(yīng)該考慮變量大小和堆棧大小是否合適。一個(gè)是減少過大的臨時(shí)變量和動態(tài)申請內(nèi)存,另一個(gè)是在SRAM空間允許的情況下增大堆棧大小,如上圖中棧大小是1024字節(jié),堆大小是512字節(jié)。
我們知道,堆棧的設(shè)置是在startup_stm32fxxx.s中設(shè)置的,但是startup_stm32fxxx.s文件是只讀的,無法修改,只需要設(shè)置一下該文件的屬性,把只讀取消即可修改。
另外,F(xiàn)LASH和SRAM起始地址在Options中可以查看:
還是在xxx.mp中,我們可以看到SRAM的分配,如下圖所示。
從上圖中可以看出SRAM空間用來存放:
1.各個(gè)文件中聲明和定義的全局變量、靜態(tài)數(shù)據(jù)和常量;
2.未初始化的全局變量;
3.HEAP區(qū);
4.STACK區(qū)。
堆在使用時(shí)會從低地址往上加,而棧是從__initial_sp開始往下減。以上圖中的堆棧地址為例,malloc會從0x200000C8開始往上加,局部變量的分配會從0x200002C8開始往下減。如果入棧元素過大,使得入棧元素的地址訪問到了0x2000068C之后的內(nèi)容,就發(fā)生了棧溢出,首先會改變堆中的元素值,如果入棧元素夠大,可能會直接改變HEAP后面的全局變量。同理,當(dāng)動態(tài)申請的內(nèi)存過大時(shí),堆中變量越界到棧中,此時(shí)就發(fā)送堆溢出。
【注】棧:向低地址擴(kuò)展,堆:向高地址擴(kuò)展。如果依次定義變量,先定義的棧變量的內(nèi)存地址比后定義的棧變量的內(nèi)存地址要大,先定義的堆變量的內(nèi)存地址比后定義的堆變量的內(nèi)存地址要小。
當(dāng)然啦,如果使用J-link調(diào)試程序,也能查看堆棧大小,棧頂指針就是使用SRAM的大小。
【Tips】
1、堆棧的大小在編譯器編譯之后是不知道的,只有運(yùn)行的時(shí)候才知道,所以需要注意一點(diǎn),就是別造成堆棧溢出了,不然就會發(fā)生hardfault錯(cuò)誤。
2、所有在處理的函數(shù),包括函數(shù)嵌套,遞歸,等等,都是從這個(gè)“棧”里面,來分配的。所以,如果棧大小為2K,一個(gè)函數(shù)的局部變量過多,比如在函數(shù)里面定義一個(gè)char buf[512],這一下就占了1/4的棧大小了,再在其他函數(shù)里面來搞兩下,程序崩潰是很容易的事情,這時(shí)候,一般你會進(jìn)入到hardfault…。
3、STM32的棧,是向下生長的。事實(shí)上,一般CPU的棧增長方向,都是向下的。而堆的生長方向,都是向上的。堆和棧,只是他們各自的起始地址和增長方向不同,他們沒有一個(gè)固定的界限,所以一旦堆棧沖突,系統(tǒng)就到了崩潰的時(shí)候了。
4、程序中的常量,如果沒加const也會編譯到SRAM里,加了const會被編譯到flash中。
3.4 實(shí)例代碼分析
前面分析了那么多,下面通過一個(gè)實(shí)例來驗(yàn)證前面的分析。
main.c函數(shù)代碼如下:
/* Includes ------------------------------------------------------------------*/
#include "stm32f1_bsp_usart.h"
#include "stm32f1_bsp_led.h"
#include "stm32f1_bsp_systick.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
uint8_t buffer[10];//聲明了一個(gè)初始化為0的全局?jǐn)?shù)組
uint8_t data = 1;//初始化的全局變量
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/**
* @brief mian
* @param None
* @retval int
*/
int main(void)
{
ST_BSP_LED_Dev BSP_LED_Dev0 = LED_DEV0_CONFIG;
ST_BSP_LED_Dev BSP_LED_Dev1 = LED_DEV1_CONFIG;
ST_BSP_LED_Dev BSP_LED_Dev2 = LED_DEV2_CONFIG;
ST_BSP_USART_Dev BSP_USART_Dev0 = USART_DEV0_CONFIG;
uint8_t stack_i; //未初始化的局部變量,
uint8_t stack_j = 1; //初始化的局部變量
uint8_t *pHeap1 = (uint8_t *)malloc(10);//指針pHeap指向堆區(qū)分配了一個(gè)uint8_t類型10大小的空間
uint8_t *pHeap2 = (uint8_t *)malloc(10);
/* Configure the NVIC Preemption Priority Bits */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*Systick init*/
SysTick_Init();
/*LED init*/
BSP_LED_Init(&BSP_LED_Dev0);
BSP_LED_Init(&BSP_LED_Dev1);
BSP_LED_Init(&BSP_LED_Dev2);
/* USART1 配置模式為 115200 8-N-1,中斷接收 */
BSP_USART_Init(&BSP_USART_Dev0, 115200, 0, 1);
printf("未初始化的全局變量 buffer 的首地址:0x%p\\r\\n", buffer);
printf("初始化的全局變量 data 的地址:0x%p\\r\\n", &data);
printf("未初始化的局部變量 stack_i 的地址:0x%p\\r\\n", &stack_i);
printf("初始化的局部變量 stack_j 的地址:0x%p\\r\\n", &stack_j);
printf("pHeap1 在堆區(qū)首地址:0x%p\\r\\n", pHeap1);
printf("pHeap2 在堆區(qū)首地址:0x%p\\r\\n", pHeap2);
free(pHeap1);
free(pHeap2);
while(1)
{
BSP_LED_Toggle(&BSP_LED_Dev0);
Delay_ms(500);
BSP_LED_Toggle(&BSP_LED_Dev1);
Delay_ms(500);
BSP_LED_Toggle(&BSP_LED_Dev2);
Delay_ms(500);
}
}
編譯后map文件中內(nèi)存分配如下:
運(yùn)行程序,打印信息如下:
data是初始化的全局變量,在.data區(qū);buffer是未初始化的全局變量,在.bss區(qū);pHeap是通過malloc分配的空間,在堆區(qū),逐漸增加;局部變量都在棧區(qū),增加減小。
4 堆棧的使用總結(jié)
堆的使用:
1、堆的使用是要結(jié)合malloc函數(shù),即使用一次malloc所得到的內(nèi)存空間既是屬于堆的空間。
2、堆的增長方向是向上,所以malloc申請的地址也是越來越大的,前提是連續(xù)申請且在最后一次申請后再釋放內(nèi)存(free)。則第一次申請的地址永遠(yuǎn)小于后面申請的地址。
3、堆是不連續(xù)的,由于RAM中還存在局部變量,代碼段和棧等等,所以動態(tài)分配的內(nèi)存是取暫時(shí)空閑的內(nèi)存,而不是預(yù)先劃出一塊區(qū)域,這就是動態(tài)分配內(nèi)存的好處。
4、使用堆的壞處,由于使用malloc申請內(nèi)存時(shí),不單只申請了所需的大小空間,還要額外暫用管理這部分空間的內(nèi)存,而釋放時(shí)又只釋放申請的內(nèi)存,所以使用堆會引入內(nèi)存碎片。當(dāng)然如果不是在短時(shí)間內(nèi)頻繁的使用malloc申請和free釋放內(nèi)存,那么操作系統(tǒng)就有足夠的時(shí)間來回收碎片空間。
棧的使用:
1、由編譯器分配,目的是將RAM劃分處一塊區(qū)域供程序運(yùn)行時(shí)的局部變量參數(shù)等使用;
2、棧是一塊連續(xù)的內(nèi)存空間,由上往下增長,即使用棧時(shí)地址是會越來越小的,如先聲明的局部變量比后聲明的地址要高;
3、棧是由程序(操作系統(tǒng))自動分配,不會有內(nèi)存碎片的問題;
4、棧的壞處:棧是固定且連續(xù)的一個(gè)大小,如果使用局部變量等超出了棧的大小則會造成內(nèi)存溢出,而編譯器通常是發(fā)現(xiàn)不了的,只有當(dāng)程序運(yùn)行到那個(gè)函數(shù)時(shí)才會發(fā)生的。這就會引入很難查找的bug。另外如如果使用malloc申請的內(nèi)存不規(guī)范使用,當(dāng)釋放內(nèi)存后,沒將指針地址清空,仍指向那個(gè)地址剛好是棧的地址,則會造成越界訪問。
-
存儲器
+關(guān)注
關(guān)注
38文章
7484瀏覽量
163762 -
keil
+關(guān)注
關(guān)注
68文章
1212瀏覽量
166840 -
編譯器
+關(guān)注
關(guān)注
1文章
1623瀏覽量
49108 -
內(nèi)存管理
+關(guān)注
關(guān)注
0文章
168瀏覽量
14134 -
Cortex-M
+關(guān)注
關(guān)注
2文章
229瀏覽量
29752
發(fā)布評論請先 登錄
相關(guān)推薦
評論