keilc51中如何看堆棧的分配情況
片內(nèi)數(shù)據(jù)存儲(chǔ)器RAM
片內(nèi)RAM有256個(gè)字節(jié),其中00H~7FH地址空間是直接尋址區(qū),該區(qū)域內(nèi)從00H~1FH地址為工作寄存器區(qū),
安排了4組工作寄存器,每組都為R0~R7,在某一時(shí)刻,CPU只能使用其中任意一組工作寄存器,由程序狀態(tài)字
PSW中RS0和RS1的狀態(tài)決定。
片內(nèi)RAM的20H~2FH地址單元為位尋址區(qū),其中每個(gè)字節(jié)的每一位都規(guī)定了位地址。每個(gè)地址單元除了可進(jìn)行字
節(jié)操作之外,還可進(jìn)行位操作。
片內(nèi)RAM的80H~FFH地址空間是特殊功能寄存器SFR區(qū),對于51子系列在該區(qū)域內(nèi)安排了21個(gè)特殊功能寄存器,對于52子系列
則在該區(qū)域內(nèi)安排了26個(gè)特殊功能寄器,同時(shí)擴(kuò)展了128個(gè)字節(jié)的間接尋址片內(nèi)RAM,地址也為80~FFH,與SFR區(qū)地址重迭.
當(dāng)我做完變量手工優(yōu)化工作后,把編譯模式設(shè)為SMALL,這樣C51編譯器會(huì)自動(dòng)把那些我沒手工指定存放區(qū)的變量優(yōu)先安排進(jìn)data區(qū),如果超出有效地址范圍,它會(huì)報(bào)錯(cuò),因此我大可以放心。按下rebuild all按鈕后,編譯器提示:
Program Size: data=236.2 xdata=19321 code=43372
"ipphone_main" - 0 Error(s), 0Warning(s).
編譯器提示的data區(qū)包括了idata在內(nèi),按以往的經(jīng)驗(yàn)來看,data區(qū)有256個(gè)byte,程序才使用了236.2個(gè),還剩下19個(gè),沒有溢出,而xdata有32k,現(xiàn)在才使用了19k,遠(yuǎn)沒有溢出,編譯結(jié)果一切很正常。
把代碼燒錄進(jìn)芯片跑起來后,結(jié)果出人意料,從現(xiàn)象來看,上電約1秒后就自動(dòng)重啟,重啟后過1秒又重啟,非常有規(guī)律的重啟。
我沒有懷疑是編譯器的原因,當(dāng)時(shí)第一念頭是懷疑是看門狗,代碼里上電后就打開了看門狗,可能某些子程序代碼執(zhí)行時(shí)間過長,看門狗復(fù)位了,于是在有懷疑的地方插入了喂狗代碼,重新編譯后再測試,依然自動(dòng)重啟。于是干脆就把看門狗的代碼注釋了,不使用看門狗,以為這回沒問題了吧,結(jié)果出人意料,還是重啟。
我仔細(xì)想了一下,能造成8051的重啟的原因不多,一是看門狗引起的重啟,這點(diǎn)可以排除;二是某些8051支持重啟指令,我手頭上用的這款雖然支持,但我沒用過那指令,這點(diǎn)也可以排除;三是8051被強(qiáng)干擾,把取指寄存器PC的內(nèi)容改變了,改成0,于是就重啟了,這點(diǎn)也可以排除,因?yàn)槿绻F(xiàn)場有強(qiáng)干擾,沒優(yōu)化前也會(huì)重啟才對。
由于沒想出來是什么原因,于是開始折騰,把優(yōu)化的變量一個(gè)個(gè)恢復(fù)成未恢復(fù)優(yōu)化的狀態(tài),每恢復(fù)一步就重新測試一次。終于在恢復(fù)一個(gè)16字節(jié)的數(shù)組時(shí)發(fā)現(xiàn)程序正常了,仔細(xì)看了一下,那數(shù)組定義在xdata區(qū)的時(shí)候程序就完全正常,而定義在idata區(qū)的時(shí)候程序就復(fù)位了,雖然奇怪的是,定義在idata區(qū)時(shí),編譯器并沒有報(bào)告內(nèi)存溢出。跟蹤匯編指令也沒發(fā)現(xiàn)異常,無論定義在idata還是xdata,編譯器為該數(shù)組分配的地址證明確實(shí)都是有效地址,確實(shí)沒有溢出,編譯器的安排還是正確。
雖然還沒找到根源,但問題既然是出現(xiàn)在內(nèi)存上,我于是決定查看當(dāng)那個(gè)數(shù)組指定為idata類型時(shí)的內(nèi)存分配。Keil C51在編譯時(shí)會(huì)輸出一個(gè)M51文件,該文件包含了大量的內(nèi)存分配信息,非常詳細(xì),包括哪個(gè)變量被編譯器分配到哪個(gè)內(nèi)存地址,占用多少個(gè)字節(jié),哪些變量是局部變量,可以重復(fù)利用……這個(gè)M51文件里都有詳細(xì)的列表。
從列表里的變量分配地址一路看下來,都沒錯(cuò),邊看還邊驚嘆編譯器對變量的分配安排非常精確,但看到最后一個(gè)堆棧指針的安排時(shí),終于發(fā)現(xiàn)問題所在了,它是這樣安排的:
TYPE BASE LENGTH RELOCATION SEGMENT NAME
----------------------------------------------------------------------------------------------
IDATA 0080H 0034H UNIT _IDATA_GROUP_
IDATA 00B4H 0022H UNIT ?ID?IPPHONE_MAIN
IDATA 00D6H 001FH UNIT ?ID?DNS_NICRCV?IPPHONE_DNS
IDATA 00F5H 0004H UNIT ?ID?DISP
IDATA 00F9H 0001H UNIT ?STACK
這上面標(biāo)有STACK的段就是堆棧分配,上面的數(shù)據(jù)表明,SP堆棧指針安排在F9H這個(gè)地址,堆??臻g是1個(gè)字節(jié)!表面看沒有溢出,但我的程序里使用了中斷服務(wù),進(jìn)入中斷服務(wù)時(shí),至少需要8個(gè)字節(jié)的堆??臻g(保存R0~R7寄存器)來進(jìn)行保護(hù)現(xiàn)場,8051使用的是遞增壓棧的設(shè)計(jì),堆棧指針往往被安排在內(nèi)存空間的后面可用部分,每壓棧一個(gè)字節(jié),SP指針往上加1,進(jìn)中斷服務(wù)時(shí),至少壓棧8個(gè)字節(jié),F(xiàn)9H+8,超出了FFH,堆棧指針不能超過FFH,也就是說堆棧溢出了!原來這就是導(dǎo)致程序不斷重啟的原因,不是變量內(nèi)存溢出,而是堆棧溢出!
而當(dāng)我把那個(gè)數(shù)組指定為xdata類型后,由于該數(shù)組不再占用idata區(qū),于是IDATA一下子多了16個(gè)字節(jié)的可用空間,重新編譯后的M51這樣安排:
IDATA 0080H 0024H UNIT _IDATA_GROUP_
IDATA 00A4H 0022H UNIT ?ID?IPPHONE_MAIN
IDATA 00C6H 001FH UNIT ?ID?DNS_NICRCV?IPPHONE_DNS
IDATA 00E5H 0004H UNIT ?ID?DISP
IDATA 00E9H 0001H UNIT ?STACK
從這組數(shù)據(jù)來看,SP指針安排到在E9H這個(gè)地址,堆??臻g有FFH-E9H+1=23個(gè)字節(jié),對于程序來說已經(jīng)夠用,因此程序運(yùn)行正常。
多次調(diào)整變量類型的編譯結(jié)果表明,C51對于堆??臻g需求大小不作計(jì)算,任何代碼都只是按堆??臻g只有1個(gè)字節(jié)需求來分配(在我眼里看來這明顯是胡來,稍復(fù)雜點(diǎn)的子程序調(diào)用都不可能只要1個(gè)字節(jié)就能完成現(xiàn)場保護(hù)),由于堆棧只能分配在data區(qū)和idata區(qū),因此當(dāng)一個(gè)程序?yàn)榱藘?yōu)化而data區(qū)占用太多時(shí),雖然編譯器能編譯成功,但往往SP堆棧指針被分配在data區(qū)的最后面,很容易造成堆??臻g不夠而溢出。為保險(xiǎn)起見,最好保證編譯后的SP值安排在F0H之前,那樣至少有16個(gè)字節(jié)的堆??臻g,才能最大限度保證程序不會(huì)跑飛。
看樣子不能太相信Keil C51,以后編譯完后,還得查看一下M51才能確保程序的質(zhì)量,不知道這個(gè)算不算Keil C51的bug。
評(píng)論