一、全局變量和局部變量
全局變量和局部變量的區(qū)別在于作用域的不同。此外還有靜態(tài)全局變量和靜態(tài)局部變量。
全局變量作用域?yàn)槿?,在一個源文件中定義,其他的源文件也可以應(yīng)用。在其他的源文件中使用extern加以聲明;
靜態(tài)全局變量作用域?yàn)樵撛次募?,只作用在聲明它的源文件中,通過static聲明,這樣即使在其他的源文件中有相同名稱的變量也不相同;
局部變量作用域?yàn)楹瘮?shù)內(nèi)部。當(dāng)函數(shù)被調(diào)用時,為其分配空間,函數(shù)調(diào)用完成后收回內(nèi)存,銷毀變量;
局部靜態(tài)變量作用域?yàn)榫植浚槐怀跏蓟淮?,但是在后面它一直存在。即使函?shù)調(diào)用完成后也一直存在。
全局變量和局部變量都是要分配內(nèi)存的,它們的區(qū)別只是在于作用域不同。
在C中,全局變量、靜態(tài)全局變量及靜態(tài)局部變量都在靜態(tài)存儲區(qū),由于這些變量的生存周期較長,占有較多的內(nèi)存但是由于已經(jīng)分配好了內(nèi)存,因此速度較快,而局部變量則在棧中分配空間。在KeilC51中這是不同的。
二、C51中的全局變量和局部變量
在51中程序ROM和數(shù)據(jù)RAM是嚴(yán)格分開的,特殊功能寄存器與片內(nèi)數(shù)據(jù)存儲器統(tǒng)一編址。這與其他一般的微機(jī)不同。51中內(nèi)部的RAM有256字節(jié),外部可尋址64KB,對于256字節(jié),其中前128字節(jié)(00-7FH)又分為三部分:通用寄存器組、可位尋址區(qū)、用戶RAM區(qū);高128字節(jié)(7F-FF)為SFR。上電復(fù)位后堆棧指針指向07H,在通用寄存器區(qū),此時對戰(zhàn)區(qū)占用1,2,3組寄存器,但是用戶可自行將sp設(shè)置在30-7F。
C51編譯器通過將變量定義為不同的類型,來區(qū)分不同的存儲區(qū),常用的變量類型有:
data:片內(nèi)RAM的低128字節(jié)
bdata:可位尋址的片內(nèi)RAM
以上兩種類型可以快速的存取數(shù)據(jù),常用來放臨時性的傳遞變量或使用頻率較高的變量。
idata:整個片內(nèi)RAM。
xdata:片外存儲區(qū)(64KB),由于在對片外存儲區(qū)操作時,需要先將數(shù)據(jù)移到片內(nèi),進(jìn)行處理后再存儲到片外,因此常用來存放不常用的變量,或收集待處理的數(shù)據(jù),或存放要被發(fā)往另一臺計(jì)算機(jī)的數(shù)據(jù)。
pdata:屬于xdata類型,由于它的高字節(jié)保存在P2口中,只能尋址256字節(jié)。
code:ROM內(nèi),數(shù)據(jù)不會丟失。
此外,C51還有三種存儲模式:SMALL, COMPACT, LARGE
SMALL模式下,如果不做特別說明,參數(shù)及局部變量默認(rèn)為data型,放在片內(nèi)RAM128字節(jié)內(nèi),訪問迅速。由于內(nèi)部的RAM有限,如果變量過多,會導(dǎo)致頻繁的使用寄存器,而使代碼變的冗長。此時棧也在片內(nèi)的RAM,棧長很關(guān)鍵,因?yàn)闂iL依賴于不同函數(shù)的嵌套層數(shù)。
COMPACT:不做特別說明,參數(shù)及局部變量默認(rèn)為pdata,??臻g在內(nèi)部RAM。
LARGE:參數(shù)及局部變量默認(rèn)為xdata,使用DPTR來尋址。訪問效率低,此外這種數(shù)據(jù)指針不能對稱操作。
全局變量會根據(jù)定義的類型或者存儲的模式分配在相應(yīng)的存儲區(qū)內(nèi),有固定的地址,如果全局變量過多則會導(dǎo)致占用太多內(nèi)存,處理速度變慢。
三、共享和覆蓋
由于51的存儲區(qū)有限,因此變有了覆蓋和共享的概念。
共享:有共享變量和共享函數(shù),共享是針對全局變量或靜態(tài)變量而言的,對全局變量定義后就對其分配了內(nèi)存,在任何函數(shù)或者程序中都可以共享該變量的內(nèi)存,在其他的文件中也可以通過聲明extern來實(shí)現(xiàn)共享。共享函數(shù)也是類同。
覆蓋:如果一個程序不再被調(diào)用,也不由其他的程序調(diào)用,在其他的程序運(yùn)行之前程序也不在運(yùn)行,那么這個程序的變量可以放在與其他的程序完全相同的RAM空間,這就是覆蓋。
對于函數(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ù)中定義的動態(tài)局部變量可以被覆蓋,一個函數(shù)說明的變量在下一次進(jìn)入函數(shù)時不同,即函數(shù)調(diào)用時會發(fā)生變化(函數(shù)調(diào)用時才為局部變量分配空間,因此每次調(diào)用分配的地址可能不同)。有一些編譯器通常把局部變量放在堆棧上,這樣運(yùn)行起來位置不固定,棧操作不方便,而在51的編譯器中則監(jiān)視函數(shù)調(diào)用的嵌套順序,把幾個函數(shù)的變量放在同樣固定的位置。在51編譯器中連接器會搜索所有函數(shù)中局部變量占用存儲區(qū)間最多的函數(shù),然后已這個函數(shù)的局部變量的占用的空間的多少開辟一片空間,其他函數(shù)的局部變量也放在該空間中,同時實(shí)現(xiàn)了變量的覆蓋(無相互調(diào)用)與地址的共享。例如函數(shù)A占10個字節(jié),函數(shù)B占20個字節(jié),函數(shù)C占15個字節(jié),如果它們之間沒有相互調(diào)用則僅需20個字節(jié)就可以滿足45個字節(jié)的變量需要。
C51中根據(jù)變量定義是的數(shù)據(jù)類型在相應(yīng)的存儲區(qū)內(nèi)為變量分配內(nèi)存,在內(nèi)部整個RAM區(qū)中,先為定義在該端的全局變量分配地址,然后是程序中所有函數(shù)的參數(shù)和局部變量的覆蓋區(qū)(內(nèi)部RAM存取迅速)。以上兩部分內(nèi)存都是上面所說的C中的靜態(tài)存儲區(qū)(段)。接下來就是系統(tǒng)的堆棧,棧底有?STACK自動生成,棧頂在0xFF。
正是由于所有函數(shù)的參數(shù)和局部變量的共享一個覆蓋區(qū),函數(shù)沒有相互的調(diào)用時,在執(zhí)行一個函數(shù)時,會將另一個函數(shù)的變量的存儲區(qū)覆蓋。如果函數(shù)有調(diào)用,那么不會覆蓋原來函數(shù)的局部變量的區(qū)間,但如果函數(shù)的嵌套(遞歸)層數(shù)太多,所有的變量的內(nèi)存大于了覆蓋區(qū)時,一個函數(shù)的內(nèi)部的變量可能會被新調(diào)用的函數(shù)沖掉,再返回該函數(shù)時,無法找到相應(yīng)變量的內(nèi)存,也就無法找到該變量的值。通過聲明為可重入函數(shù),讓參數(shù)和變量放在堆棧中。
對于覆蓋,大多人認(rèn)為是為了節(jié)省內(nèi)存,但網(wǎng)上有另一種說法,個人覺得很有道理,引用如下:
一般的C編譯器(或者更確切點(diǎn)地說:基于一般的處理器上的C編譯器),其函數(shù)的局部變量是存放于堆棧中的,而C51是存放于一個可覆蓋的(數(shù)據(jù))段中的.
至于C51這樣做的原因,不是象有些人說的那樣,為了節(jié)約內(nèi)存.事實(shí)上,這樣做根本節(jié)約不了內(nèi)存.理由如下:
1) 如果一個函數(shù)func1調(diào)用另一個函數(shù)func2,那么func1,func2的局部變量根本就不能是同一塊內(nèi)存.C51還是要為他們分配不同的RAM.這跟使用堆棧相比,節(jié)約不了內(nèi)存.
2) 如果func1,func2不是在一個調(diào)用鏈上,那么C51可以通過覆蓋分析,讓它們的局部變量共享相同的內(nèi)存地址.但這樣也不會比使用堆棧節(jié)約內(nèi)存.因?yàn)榧热凰鼈兪窃诓煌恼{(diào)用鏈上,那么當(dāng)其中一個函數(shù)運(yùn)行時,那么另外一個函數(shù)必然不在其生命期內(nèi),它所占用的堆棧也已釋放,歸還給系統(tǒng).
真實(shí)的原因(C51使用覆蓋段作為局部變量的存放地的原因)是:
51的指令系統(tǒng)沒有一個有效的相對尋址(變址尋址)的指令,這使得使用堆棧作為變量的代價太過昂貴.
使用堆棧存放變量的一般做法是:
進(jìn)入函數(shù)時,保留一段堆??臻g,作為變量的存放空間,用一個可作為基址尋址的寄存器指向這個空間,通過加上一個偏移量,就可以訪問不同的變量了.
例如: MOV EAX, [EBP + 14];X86指令
LDR R0, [R12, #14];ARM指令
都可以很好的解決這個問題.
但51缺少這樣的指令.
*其實(shí),51中還是有2個可變址尋址的指令的,但不適合訪問堆棧的局部變量這樣的場合.
MOVC A, @A+DPTR
MOVC A, @A+PC
所以,C51有個特別的關(guān)鍵字: reentrant 用來解決函數(shù)重入的問題。