在開源電子中看到一篇文章講的是棧增長和大端/小端問題。學(xué)C語言的時(shí)候,我們知道堆棧的區(qū)別:
(1)棧區(qū)(stack):由編譯器自動(dòng)分配和釋放,存放函數(shù)的參數(shù)值、局部變量的值等,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
?(2)堆區(qū)(heap):一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時(shí)可能由操作系統(tǒng)回收。分配方式類似于數(shù)據(jù)結(jié)構(gòu)中的鏈表。
?(3)全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。程序結(jié)束后由系統(tǒng)自動(dòng)釋放。
?(4)文字常量區(qū):常量字符串就是存放在這里的。
?(5)程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼。
下面的帖子:主要意思是要證明stm32是小端模式,堆從RAM的起始地址處(0x2000 0000)分配內(nèi)存給全局變量和靜態(tài)變量,并且堆是向上增長,棧是向下增長。
1,首先來看:棧(STACK)的問題。
函數(shù)的局部變量,都是存放在“?!崩锩?,棧的英文是:STACK.STACK的大小,我們可以在stm32的啟動(dòng)文件里面設(shè)置,以戰(zhàn)艦stm32開發(fā)板為例,在startup_stm32f10x_hd.s里面,開頭就有:
Stack_Size EQU 0x00000800
表示棧大小是0X800,也就是2048字節(jié)。這樣,CPU處理任務(wù)的時(shí)候,函數(shù)局部變量做多可占用的大小就是:2048字節(jié),注意:是所有在處理的函數(shù),包括函數(shù)嵌套,遞歸,等等,都是從這個(gè)“棧”里面,來分配的。
所以,如果一個(gè)函數(shù)的局部變量過多,比如在函數(shù)里面定義一個(gè)u8 buf[512],這一下就占了1/4的棧大小了,再在其他函數(shù)里面來搞兩下,程序崩潰是很容易的事情,這時(shí)候,一般你會(huì)進(jìn)入到hardfault.。。。
這是初學(xué)者非常容易犯的一個(gè)錯(cuò)誤。切記不要在函數(shù)里面放N多局部變量,尤其有大數(shù)組的時(shí)候!
對(duì)于棧區(qū),一般棧頂,也就是MSP,在程序剛運(yùn)行的時(shí)候,指向程序所占用內(nèi)存的最高地址。比如附件里面的這個(gè)程序序,內(nèi)存占用如下圖:
圖中,我們可以看到,程序總共占用內(nèi)存:20+2348字節(jié)=2368=0X940那么程序剛開始運(yùn)行的時(shí)候:MSP=0X2000 0000+0X940=0X2000 0940.事實(shí)上,也是如此,如圖:
圖中,MSP就是:0X2000 0940.程序運(yùn)行后,MSP就是從這個(gè)地址開始,往下給函數(shù)的局部變量分配地址。
再說說棧的增長方向,我們可以用如下代碼測試:
?。踥bjc] view plain copy//保存棧增長方向
//0,向下增長;1,向上增長。
static u8 stack_dir;
//查找棧增長方向,結(jié)果保存在stack_dir里面。
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一個(gè)dummy的地址。
u8 dummy; //用于獲取棧地址
if(addr==NULL) //第一次進(jìn)入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //遞歸
}else //第二次進(jìn)入
{
if(&dummy》addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么說明棧增長方向是向上的。
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么說明棧增長方向是向下的。
}
}
這個(gè)代碼不是我寫的,網(wǎng)上抄來的,思路很巧妙,利用遞歸,判斷兩次分配給dummy的地址,來比較棧是向下生長,還是向上生長。
如果你在STM32測試這個(gè)函數(shù),你會(huì)發(fā)現(xiàn),STM32的棧,是向下生長的。事實(shí)上,一般CPU的棧增長方向,都是向下的。
2,再來說說,堆(HEAP)的問題。
全局變量,靜態(tài)變量,以及內(nèi)存管理所用的內(nèi)存,都是屬于“堆”區(qū),英文名:“HEAP”與棧區(qū)不同,堆區(qū),則從內(nèi)存區(qū)域的起始地址,開始分配給各個(gè)全局變量和靜態(tài)變量。
堆的生長方向,都是向上的。在程序里面,所有的內(nèi)存分為:堆+棧。 只是他們各自的起始地址和增長方向不同,他們沒有一個(gè)固定的界限,所以一旦堆棧沖突,系統(tǒng)就到了崩潰的時(shí)候了。
同樣,我們用附件里面的例程測試:
stack_dir的地址是0X20000004,也就是STM32的內(nèi)存起始端的地址。
這里本來應(yīng)該是從0X2000 0000開始分配的,但是,我仿真發(fā)現(xiàn)0X2000 0000總是存放:0X2000 0398,這個(gè)值,貌似是MSP,但是又不變化,還請(qǐng)高手幫忙解釋下。
其他的,全局變量,則依次遞增,地址肯定大于0X20000004,比如cpu_endian的地址就是0X20000005.
這就是STM32內(nèi)部堆的分配規(guī)則。
3,再說說,大小端的問題。
大端模式:低位字節(jié)存在高地址上,高位字節(jié)存在低地址上
小端模式:高位字節(jié)存在高地址上,低位字節(jié)存在低地址上
STM32屬于小端模式,簡單的說,比如u32 temp=0X12345678;
假設(shè)temp地址在0X2000 0010.
那么在內(nèi)存里面,存放就變成了:
地址 | HEX |
0X2000 0010 | 78 56 43 12 |
CPU到底是大端還是小端,可以通過如下代碼測試:
//CPU大小端
//0,小端模式;1,大端模式。
static u8 cpu_endian;
//獲取CPU大小端模式,結(jié)果保存在cpu_endian里面
void find_cpu_endian(void)
{
int x=1;
if(*(char*)&x==1)cpu_endian=0; //小端模式
else cpu_endian=1; //大端模式
}
以上測試,在STM32上,你會(huì)得到cpu_endian=0,也就是小端模式。
4,最后說說,STM32內(nèi)存的問題。
還是以附件工程為例,在前面第一個(gè)圖,程序總共占用內(nèi)存:20+2348字節(jié),這么多內(nèi)存,到底是怎么得來的呢?
我們可以雙擊Project側(cè)邊欄的:Targt1,會(huì)彈出test.map,在這個(gè)里面,我們就可以清楚的知道這些內(nèi)存到底是怎么來的了。在這個(gè)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字節(jié)
112 12 0 0 0 427 led.o
72 26 304 0 2048 828 startup_stm32f10x_hd.o //啟動(dòng)文件,里面定義了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字節(jié)。
384 24 0 8 200 3050 usart.o//usart.c定義了一個(gè)串口接收數(shù)組buffer,占用200字節(jié)。
----------------------------------------------------------------------
1800 278 336 20 2248 216735 Object Totals //總共2248+20字節(jié)
0 0 32 0 0 0 (incl. Generated)
0 0 0 2 0 0 (incl. Padding)//2字節(jié)用于對(duì)其
----------------------------------------------------------------------
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字節(jié)
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 //調(diào)用的庫用了100字節(jié)。
10 0 2 0 4 0 (incl. Padding) //用于對(duì)其多占用了4個(gè)字節(jié)
----------------------------------------------------------------------
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)
==============================================================================
通過這個(gè)文件,我們就可以分析整個(gè)內(nèi)存,是怎么被占用的,具體到每個(gè)文件,占用多少。一目了然了。
5,最后,看看整個(gè)測試代碼:
main.c代碼如下,工程見附件。
?。踥bjc] view plain copy#include “sys.h”
#include “usart.h”
#include “delay.h”
#include “l(fā)ed.h”
#include “beep.h”
#include “key.h”
//ALIENTEK戰(zhàn)艦STM32開發(fā)板堆棧增長方向以及CPU大小端測試
//保存棧增長方向
//0,向下增長;1,向上增長。
static u8 stack_dir;
//CPU大小端
//0,小端模式;1,大端模式。
static u8 cpu_endian;
//查找棧增長方向,結(jié)果保存在stack_dir里面。
void find_stack_direction(void)
{
static u8 *addr=NULL; //用于存放第一個(gè)dummy的地址。
u8 dummy; //用于獲取棧地址
if(addr==NULL) //第一次進(jìn)入
{
addr=&dummy; //保存dummy的地址
find_stack_direction (); //遞歸
}else //第二次進(jìn)入
{
if(&dummy》addr)stack_dir=1; //第二次dummy的地址大于第一次dummy,那么說明棧增長方向是向上的。
else stack_dir=0; //第二次dummy的地址小于第一次dummy,那么說明棧增長方向是向下的。
}
}
//獲取CPU大小端模式,結(jié)果保存在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); //系統(tǒng)時(shí)鐘設(shè)置
uart_init(72,9600); //串口初始化為9600
delay_init(72); //延時(shí)初始化
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;
}
}
測試結(jié)果如圖:
有人提出全局變量和靜態(tài)變量是存儲(chǔ)在靜態(tài)區(qū)而不是堆中,并且編寫了一個(gè)測試代碼,(原帖48樓)修改了堆和棧的長度,main函數(shù)增加了初始化串口緩沖的操作,和調(diào)用了一個(gè)無限迭代的函數(shù)。堆棧位置在《test.map》line:431-435和line:779-780.F11執(zhí)行函數(shù)Iteration,會(huì)看到內(nèi)存區(qū)會(huì)不斷被Iteration的地址覆蓋,直到破壞所有靜態(tài)變量空間。如果靜態(tài)變量在棧區(qū),按照棧先進(jìn)后出的機(jī)制,應(yīng)該不會(huì)被破壞。當(dāng)然,如果鏈接器是先放棧,再放堆,最后放靜態(tài)變量,就做不了這個(gè)實(shí)驗(yàn)了。再三實(shí)驗(yàn),也認(rèn)可了這種說法,做了如下的修改:
一、內(nèi)存基本構(gòu)成
可編程內(nèi)存在基本上分為這樣的幾大部分:靜態(tài)存儲(chǔ)區(qū)、堆區(qū)和棧區(qū)。他們的功能不同,對(duì)他們使用方式也就不同。
靜態(tài)存儲(chǔ)區(qū):內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在。它主要存放靜態(tài)數(shù)據(jù)、全局?jǐn)?shù)據(jù)和常量。
棧區(qū):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
堆區(qū):亦稱動(dòng)態(tài)內(nèi)存分配。程序在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意大小的內(nèi)存,程序員自己負(fù)責(zé)在適當(dāng)?shù)臅r(shí)候用free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生存期可以由我們決定,如果我們不釋放內(nèi)存,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存。 但是,良好的編程習(xí)慣是:如果某動(dòng)態(tài)內(nèi)存不再使用,需要將其釋放掉,否則,我們認(rèn)為發(fā)生了內(nèi)存泄漏現(xiàn)象。
按照這個(gè)說法,我在.s文件里面設(shè)置了:
Heap_Size EQU 0x00000000
也就是,沒有任何動(dòng)態(tài)內(nèi)存分配。
這樣,內(nèi)存=靜態(tài)存儲(chǔ)區(qū)+棧區(qū)了。
不存在堆!??!
因?yàn)槲覜]有用malloc來動(dòng)態(tài)分配內(nèi)存。
因此,前面提到的一切堆區(qū),其實(shí)就是靜態(tài)存儲(chǔ)區(qū)。
另外,經(jīng)過測試,確實(shí)是這樣。
STM32的內(nèi)存分配,應(yīng)該分為兩種情況。
1,使用了系統(tǒng)的malloc。
2,未使用系統(tǒng)的malloc。
第一種情況(使用malloc):
STM32的內(nèi)存分配規(guī)律:
從0X20000000開始依次為:靜態(tài)存儲(chǔ)區(qū)+堆區(qū)+棧區(qū)
第二種情況(不使用malloc):
STM32的內(nèi)存分配規(guī)律:
從0X20000000開始依次為:靜態(tài)存儲(chǔ)區(qū)+棧區(qū)
第二種情況不存在堆區(qū)。
所以,一般對(duì)于我們開發(fā)板例程,實(shí)際上,沒有所謂堆區(qū)的概念,而僅僅是:靜態(tài)存儲(chǔ)區(qū)+棧區(qū)。
無論哪種情況,所有的全局變量,包括靜態(tài)變量之類的,全部存儲(chǔ)在靜態(tài)存儲(chǔ)區(qū)。
緊跟靜態(tài)存儲(chǔ)區(qū)之后的,是堆區(qū)(如沒用到malloc,則沒有該區(qū)),之后是棧區(qū)。
評(píng)論
查看更多