C51變量的存儲(chǔ)
一、全局變量和局部變量
全局變量和局部變量的區(qū)別在于作用域的不同。此外還有靜態(tài)全局變量和靜態(tài)局部變量。
全局變量作用域?yàn)槿?,在一個(gè)源文件中定義,其他的源文件也可以應(yīng)用。在其他的源文件中使用extern加以聲明;
靜態(tài)全局變量作用域?yàn)樵撛次募蛔饔迷诼暶魉脑次募?,通過(guò)static聲明,這樣即使在其他的源文件中有相同名稱的變量也不相同;
局部變量作用域?yàn)楹瘮?shù)內(nèi)部。當(dāng)函數(shù)被調(diào)用時(shí),為其分配空間,函數(shù)調(diào)用完成后收回內(nèi)存,銷毀變量;
局部靜態(tài)變量作用域?yàn)榫植?,只被初始化一次,但是在后面它一直存在。即使函?shù)調(diào)用完成后也一直存在。
全局變量和局部變量都是要分配內(nèi)存的,它們的區(qū)別只是在于作用域不同。
在C中,全局變量、靜態(tài)全局變量及靜態(tài)局部變量都在靜態(tài)存儲(chǔ)區(qū),由于這些變量的生存周期較長(zhǎng),占有較多的內(nèi)存但是由于已經(jīng)分配好了內(nèi)存,因此速度較快,而局部變量則在棧中分配空間。在KeilC51中這是不同的。
二、C51中的全局變量和局部變量
在51中程序ROM和數(shù)據(jù)RAM是嚴(yán)格分開(kāi)的,特殊功能寄存器與片內(nèi)數(shù)據(jù)存儲(chǔ)器統(tǒng)一編址。這與其他一般的微機(jī)不同。51中內(nèi)部的RAM有256字節(jié),外部可尋址64KB,對(duì)于256字節(jié),其中前128字節(jié)(00-7FH)又分為三部分:通用寄存器組、可位尋址區(qū)、用戶RAM區(qū);高128字節(jié)(7F-FF)為SFR。上電復(fù)位后堆棧指針指向07H,在通用寄存器區(qū),此時(shí)對(duì)戰(zhàn)區(qū)占用1,2,3組寄存器,但是用戶可自行將sp設(shè)置在30-7F。
C51編譯器通過(guò)將變量定義為不同的類型,來(lái)區(qū)分不同的存儲(chǔ)區(qū),常用的變量類型有:
data:片內(nèi)RAM的低128字節(jié)
bdata:可位尋址的片內(nèi)RAM
以上兩種類型可以快速的存取數(shù)據(jù),常用來(lái)放臨時(shí)性的傳遞變量或使用頻率較高的變量。
idata:整個(gè)片內(nèi)RAM。
xdata:片外存儲(chǔ)區(qū)(64KB),由于在對(duì)片外存儲(chǔ)區(qū)操作時(shí),需要先將數(shù)據(jù)移到片內(nèi),進(jìn)行處理后再存儲(chǔ)到片外,因此常用來(lái)存放不常用的變量,或收集待處理的數(shù)據(jù),或存放要被發(fā)往另一臺(tái)計(jì)算機(jī)的數(shù)據(jù)。
pdata:屬于xdata類型,由于它的高字節(jié)保存在P2口中,只能尋址256字節(jié)。
code:ROM內(nèi),數(shù)據(jù)不會(huì)丟失。
此外,C51還有三種存儲(chǔ)模式:SMALL, COMPACT, LARGE
SMALL模式下,如果不做特別說(shuō)明,參數(shù)及局部變量默認(rèn)為data型,放在片內(nèi)RAM128字節(jié)內(nèi),訪問(wèn)迅速。由于內(nèi)部的RAM有限,如果變量過(guò)多,會(huì)導(dǎo)致頻繁的使用寄存器,而使代碼變的冗長(zhǎng)。此時(shí)棧也在片內(nèi)的RAM,棧長(zhǎng)很關(guān)鍵,因?yàn)闂iL(zhǎng)依賴于不同函數(shù)的嵌套層數(shù)。
COMPACT:不做特別說(shuō)明,參數(shù)及局部變量默認(rèn)為pdata,??臻g在內(nèi)部RAM。
LARGE:參數(shù)及局部變量默認(rèn)為xdata,使用DPTR來(lái)尋址。訪問(wèn)效率低,此外這種數(shù)據(jù)指針不能對(duì)稱操作。
全局變量會(huì)根據(jù)定義的類型或者存儲(chǔ)的模式分配在相應(yīng)的存儲(chǔ)區(qū)內(nèi),有固定的地址,如果全局變量過(guò)多則會(huì)導(dǎo)致占用太多內(nèi)存,處理速度變慢。
三、共享和覆蓋
由于51的存儲(chǔ)區(qū)有限,因此變有了覆蓋和共享的概念。
共享:有共享變量和共享函數(shù),共享是針對(duì)全局變量或靜態(tài)變量而言的,對(duì)全局變量定義后就對(duì)其分配了內(nèi)存,在任何函數(shù)或者程序中都可以共享該變量的內(nèi)存,在其他的文件中也可以通過(guò)聲明extern來(lái)實(shí)現(xiàn)共享。共享函數(shù)也是類同。
覆蓋:如果一個(gè)程序不再被調(diào)用,也不由其他的程序調(diào)用,在其他的程序運(yùn)行之前程序也不在運(yùn)行,那么這個(gè)程序的變量可以放在與其他的程序完全相同的RAM空間,這就是覆蓋。
對(duì)于函數(shù)之間的覆蓋在Keil中有一段描述:
The system the linker uses to determine which function arguments (or parameters) and variables may be overlaid is quite sophisticated. It begins when the compiler generates the object code for a function.
The compiler stores all function parameters and local variables in overlayable bit, data, pdata, or xdata segments. The segment names generated by the compiler forParameters and Local Variablesare well-defined. They are used by the compiler to access parameters and local variables.
As the linker resolves references between functions, it builds a call tree based on where those references appear. For instance, iffunction_acallsfunction_b, the compiler inserts a reference tofunction_bin the object code generated forfunction_a. When the linker resolves this reference, it inserts the address offunction_band adds a call fromfunction_atofunction_bin the call tree.
The local variables and parameters offunction_aare overlaid with the variables and parameters offunction_bonly under the following conditions:
No call references of any kind may exist betweenfunction_aandfunction_b. This includes direct calls between A and B as well as calls from other functions on the A branch to B and calls from functions on the B branch to A.
The functions A and B may be invoked by only one program event or root: either the main root or an interrupt but not both. It is impossible to overlay variables and parameters if a function is called by an interrupt and the main program or by two interrupts.
The segment definitions of functions A and B must conform to the rules for segment names described in the compiler manual.
在函數(shù)中定義的動(dòng)態(tài)局部變量可以被覆蓋,一個(gè)函數(shù)說(shuō)明的變量在下一次進(jìn)入函數(shù)時(shí)不同,即函數(shù)調(diào)用時(shí)會(huì)發(fā)生變化(函數(shù)調(diào)用時(shí)才為局部變量分配空間,因此每次調(diào)用分配的地址可能不同)。有一些編譯器通常把局部變量放在堆棧上,這樣運(yùn)行起來(lái)位置不固定,棧操作不方便,而在51的編譯器中則監(jiān)視函數(shù)調(diào)用的嵌套順序,把幾個(gè)函數(shù)的變量放在同樣固定的位置。在51編譯器中連接器會(huì)搜索所有函數(shù)中局部變量占用存儲(chǔ)區(qū)間最多的函數(shù),然后已這個(gè)函數(shù)的局部變量的占用的空間的多少開(kāi)辟一片空間,其他函數(shù)的局部變量也放在該空間中,同時(shí)實(shí)現(xiàn)了變量的覆蓋(無(wú)相互調(diào)用)與地址的共享。例如函數(shù)A占10個(gè)字節(jié),函數(shù)B占20個(gè)字節(jié),函數(shù)C占15個(gè)字節(jié),如果它們之間沒(méi)有相互調(diào)用則僅需20個(gè)字節(jié)就可以滿足45個(gè)字節(jié)的變量需要。
C51中根據(jù)變量定義是的數(shù)據(jù)類型在相應(yīng)的存儲(chǔ)區(qū)內(nèi)為變量分配內(nèi)存,在內(nèi)部整個(gè)RAM區(qū)中,先為定義在該端的全局變量分配地址,然后是程序中所有函數(shù)的參數(shù)和局部變量的覆蓋區(qū)(內(nèi)部RAM存取迅速)。以上兩部分內(nèi)存都是上面所說(shuō)的C中的靜態(tài)存儲(chǔ)區(qū)(段)。接下來(lái)就是系統(tǒng)的堆棧,棧底有?STACK自動(dòng)生成,棧頂在0xFF。
正是由于所有函數(shù)的參數(shù)和局部變量的共享一個(gè)覆蓋區(qū),函數(shù)沒(méi)有相互的調(diào)用時(shí),在執(zhí)行一個(gè)函數(shù)時(shí),會(huì)將另一個(gè)函數(shù)的變量的存儲(chǔ)區(qū)覆蓋。如果函數(shù)有調(diào)用,那么不會(huì)覆蓋原來(lái)函數(shù)的局部變量的區(qū)間,但如果函數(shù)的嵌套(遞歸)層數(shù)太多,所有的變量的內(nèi)存大于了覆蓋區(qū)時(shí),一個(gè)函數(shù)的內(nèi)部的變量可能會(huì)被新調(diào)用的函數(shù)沖掉,再返回該函數(shù)時(shí),無(wú)法找到相應(yīng)變量的內(nèi)存,也就無(wú)法找到該變量的值。通過(guò)聲明為可重入函數(shù),讓參數(shù)和變量放在堆棧中。
對(duì)于覆蓋,大多人認(rèn)為是為了節(jié)省內(nèi)存,但網(wǎng)上有另一種說(shuō)法,個(gè)人覺(jué)得很有道理,引用如下:
一般的C編譯器(或者更確切點(diǎn)地說(shuō):基于一般的處理器上的C編譯器),其函數(shù)的局部變量是存放于堆棧中的,而C51是存放于一個(gè)可覆蓋的(數(shù)據(jù))段中的.
至于C51這樣做的原因,不是象有些人說(shuō)的那樣,為了節(jié)約內(nèi)存.事實(shí)上,這樣做根本節(jié)約不了內(nèi)存.理由如下:
1) 如果一個(gè)函數(shù)func1調(diào)用另一個(gè)函數(shù)func2,那么func1,func2的局部變量根本就不能是同一塊內(nèi)存.C51還是要為他們分配不同的RAM.這跟使用堆棧相比,節(jié)約不了內(nèi)存.
2) 如果func1,func2不是在一個(gè)調(diào)用鏈上,那么C51可以通過(guò)覆蓋分析,讓它們的局部變量共享相同的內(nèi)存地址.但這樣也不會(huì)比使用堆棧節(jié)約內(nèi)存.因?yàn)榧热凰鼈兪窃诓煌恼{(diào)用鏈上,那么當(dāng)其中一個(gè)函數(shù)運(yùn)行時(shí),那么另外一個(gè)函數(shù)必然不在其生命期內(nèi),它所占用的堆棧也已釋放,歸還給系統(tǒng).
真實(shí)的原因(C51使用覆蓋段作為局部變量的存放地的原因)是:
51的指令系統(tǒng)沒(méi)有一個(gè)有效的相對(duì)尋址(變址尋址)的指令,這使得使用堆棧作為變量的代價(jià)太過(guò)昂貴.
使用堆棧存放變量的一般做法是:
進(jìn)入函數(shù)時(shí),保留一段堆棧空間,作為變量的存放空間,用一個(gè)可作為基址尋址的寄存器指向這個(gè)空間,通過(guò)加上一個(gè)偏移量,就可以訪問(wèn)不同的變量了.
例如: MOV EAX, [EBP + 14];X86指令
LDR R0, [R12, #14];ARM指令
都可以很好的解決這個(gè)問(wèn)題.
但51缺少這樣的指令.
*其實(shí),51中還是有2個(gè)可變址尋址的指令的,但不適合訪問(wèn)堆棧的局部變量這樣的場(chǎng)合.
MOVC A, @A+DPTR
MOVC A, @A+PC
所以,C51有個(gè)特別的關(guān)鍵字: reentrant 用來(lái)解決函數(shù)重入的問(wèn)題。





