[導(dǎo)讀] 從這篇文章開(kāi)始,將會(huì)不定期更新關(guān)于嵌入式C語(yǔ)言編程相關(guān)的個(gè)人認(rèn)為比較重要的知識(shí)點(diǎn),或者踩過(guò)的坑。 為什么要深入理解棧?做C語(yǔ)言開(kāi)發(fā)如果棧設(shè)置不合理或者使用不對(duì),棧就會(huì)溢出,溢出就會(huì)遇到無(wú)法預(yù)測(cè)亂飛現(xiàn)象。所以對(duì)棧的深入理解是非常重要的。 注:動(dòng)畫如果看不清楚可以電腦看更清晰 啥是棧沒(méi)有比這個(gè)更直觀的啦,棧是一種受限的數(shù)據(jù)結(jié)構(gòu)模型,其數(shù)據(jù)總是只能在頂部追加,利用一個(gè)指針進(jìn)行索引,頂端叫棧頂,相對(duì)的一端底部稱為棧底。棧是一種LIFO后入先出的數(shù)據(jù)結(jié)構(gòu)。 棧就兩種操作:
再進(jìn)一步探討: 首先將棧與堆分清,從看到這篇文章開(kāi)始,我建議你不要把堆和棧連在一起叫,棧是棧,堆是堆,這是兩回事,別混為一談?。ǘ驯疚牟簧钊胗懻摚?/p> 從C/C++編程語(yǔ)言的角度來(lái)看:
為啥用棧這個(gè)數(shù)據(jù)模型的應(yīng)用價(jià)值是什么呢?先來(lái)看一下單片機(jī)內(nèi)部的可能有哪些棧應(yīng)用?以STM32為例,參考IAR C/C++ DevelopmentGuide,P207
如果使用RTOS還有任務(wù)棧,如果是Linux,其內(nèi)核線程同樣也需要棧的支持,等等這一切的一切棧,其本質(zhì)上都是利用了棧數(shù)據(jù)模型的LIFO后入先出的特性,一個(gè)典型應(yīng)用場(chǎng)景就是比如做一件事情做到一半而要轉(zhuǎn)而去做另外一件事,對(duì)于芯片編程而言,就需要將當(dāng)前的工作做個(gè)暫存,等另外一件事情做完了,再接著回來(lái)繼續(xù)做。那么怎么做到呢,以一個(gè)中斷處理為例,要記住當(dāng)前的工作態(tài)有哪些信息需要暫存呢?PC指針,局部變量等就被壓入棧,再將中斷服務(wù)程序地址導(dǎo)入PC指針,進(jìn)而去執(zhí)行中斷服務(wù)程序,待中斷處理完畢,在將棧里的內(nèi)容按照后入先出彈出到對(duì)應(yīng)的寄存器就恢復(fù)了原程序的現(xiàn)場(chǎng),進(jìn)而繼續(xù)執(zhí)行。 怎么用棧在哪里定義大小,定多大合適?這可能很多剛接觸單片機(jī)開(kāi)發(fā)的同學(xué)不是太清楚,下面就將比較常見(jiàn)的IAR開(kāi)發(fā)環(huán)境為例如何定義棧定義棧大小的地方說(shuō)明一下,這里以IAR8.4.1為例,有兩種方式可以進(jìn)行棧大小設(shè)置。 IDE設(shè)置為了更加清楚明了,制作了一個(gè)GIF操作展示視頻,在stack/heap中就可以設(shè)置了,其中stack用于設(shè)置棧區(qū)大小,heap用于設(shè)置堆大小。 這個(gè)demo中設(shè)置了其棧的大小為0x200,堆的大小為0x400,全編譯后,檢查map文件就印證了棧/堆的大小如預(yù)期所修改。 鏈接配置文件其實(shí)對(duì)于比較熟悉的開(kāi)發(fā)人員,上一種方式并非推薦用法。用鏈接配置文件將具有更好的靈活性,比如可以指定一個(gè)段的對(duì)齊方式,存儲(chǔ)位置,某個(gè)符號(hào)的存儲(chǔ)位置等等。這里同樣為了直觀也做了一個(gè)GIF動(dòng)畫,介紹如何通過(guò)鏈接文件進(jìn)行棧/堆的大小配置。 其最終的效果也一樣如預(yù)期將棧區(qū)的大小設(shè)置好了。 棧溢出這里為了比較容易的展示棧溢出的問(wèn)題,在main函數(shù)利用遞歸方法計(jì)算階乘,代碼如下: #include <stdio.h> 為方便觀察,將stm32f407xx_flash.icf 將棧改為256字節(jié)
全編譯后通過(guò)map文件來(lái)看下棧/堆的分配情況: 'P2', part 3 of 3: 0x400 直觀些,翻譯成下圖,CSTACK段分配在0x2000 0280-0x2000 0480,堆分配在0x2000 0480-0x2000 0680。 圖為什么沒(méi)有將0x2000 07D8畫在棧區(qū)呢?通過(guò)調(diào)試發(fā)現(xiàn),這個(gè)字空間沒(méi)有用做棧的實(shí)際存儲(chǔ)。將工程設(shè)置成simulation模式,debug進(jìn)入main.o勾選掉,我們來(lái)計(jì)算20的階乘,來(lái)具體看一下: 對(duì)這個(gè)動(dòng)圖解讀一下:
棧的變化情況: stack test: 每調(diào)用一次階乘函數(shù),棧就壓入4個(gè)字,由上面還可以看到第20次進(jìn)入時(shí),棧指針為0x200005E0,如果再壓入4個(gè)字棧指針會(huì)變成0x200005C8,是這樣嗎,結(jié)果還對(duì)嗎?將n改為21編譯運(yùn)行,來(lái)看一看: 看到了吧,驚喜來(lái)了,棧溢出了,程序已經(jīng)不聽(tīng)話了,完全不知道在干嘛了。所以棧溢出的后果是極端危險(xiǎn)的,完全無(wú)法預(yù)期,程序會(huì)帶來(lái)什么后果。 總結(jié)一下
—END—
|
|
來(lái)自: 西北望msm66g9f > 《編程》