其中,靜態(tài)變量是指,在編譯時刻(Compiling-time)變量的地址和大小都已經(jīng)確定下來的變量。動態(tài)變量是指,直到運行時刻(Run-time),變量的地址(有時候包括確切大?。┎拍茉谀硞€時刻暫時性的確定下來的變量。
> 靜態(tài)變量 在嵌入式系統(tǒng)中,確定的(Deterministic)通常是“簡單可靠”的代名詞,因此在追求可靠性的嵌入式項目中盡可能使用靜態(tài)變量是有道理的。靜態(tài)變量是永恒的,如果一個程序就是一個世界,那么這些靜態(tài)變量從創(chuàng)世紀(jì)開始就存在了,直到末日它也依然在那里、地址、大小都不會變化。
靜態(tài)變量按照“語法上的作用范圍”可以劃分為:全局變量(Global Variable)和靜態(tài)變量(Static Variable)。
靜態(tài)變量的作用范圍受到花括號的限制——僅在對應(yīng)的花括號內(nèi)有效。
根據(jù)這一規(guī)則,我們?nèi)菀字?,在任何花括號?nèi)的靜態(tài)變量,都是局部靜態(tài)變量(local static variable),其作用范圍受到對應(yīng)的花括號限制。有一類特殊的靜態(tài)變量,它們的頭頂上沒有任何花括號了,而且也沒有static關(guān)鍵字的限制,那么我們可以理解為,這類無人約束的變量,其作用范圍就是整個工程啦——也就是我們所說的全局變量。還有一類頭頂上沒有花括號,但是由static修飾的靜態(tài)變量,我們稱為“模塊內(nèi)全局變量”——它僅在當(dāng)前.c文件內(nèi)是可以“全局”訪問的。
其實,根據(jù)我們之前一篇關(guān)于指針的文章(《求求你,不要再糾結(jié)指針了……》,發(fā)送關(guān)鍵字“指針”進(jìn)行閱讀),我們知道,這種“語法上”的東西都是虛假的,騙小孩的,具體就不再贅述。基于這一原因,后面將不對全局變量和靜態(tài)局部變量之流做區(qū)分,統(tǒng)一稱為靜態(tài)變量。
> 靜態(tài)變量放在哪里呢?
static uint32_t wExampleA = 0x12345678; static uint16_t hwExampleB = 0; static uint16_t *phwPointer = NULL; static uint8_t chExampleC[10]; static float fExampleD;無論是代碼(Code)還是數(shù)據(jù)(Data),他們的容器都是“段”(section)。
> 對于 wExampleA 這樣有非0初始值的變量,編譯器視作 RW Data (Read/Write Data,也就是普通的可讀可寫的數(shù)據(jù)),簡稱RW,放在“.data”段(.data section)里。
> 對于 wExampleB、phwPointer 這樣被明確初始化為0的變量,編譯器視作 ZI RW Data(Zero Initialized Read/Write Data,初始化為0的可讀可寫數(shù)據(jù)),簡稱 ZI,放在“.bss”段( .bss section)。
> 對于 wExampleC 和 wExampleD 這樣沒有明確指定初始值的變量,編譯器會視作其默認(rèn)“應(yīng)該”用0進(jìn)行初始化,因此也作為 ZI,放在“.bss”段。
簡單說,除了有非0初始化值的變量放在.data段以外,( 記憶為 RW 放 .data section),其余所有變量都放在 .bss 段(記憶為 ZI 放 .bss section)。
昏昏欲睡的高手們,福利來了: 在MDK中(其實是 ARM Compiler中),默認(rèn)情況下,所有尺寸小于8個字節(jié)、本應(yīng)放在 .bss 段的 ZI Data,都會被作為普通RW Data放在 “.bss” 段——之所以這么做是因為編譯器覺得:通過循環(huán)賦值的方法給這幫小變量初始化成0太不劃算了,初始化他們的程序都比變量本身還大呢,干脆放幾個0到RW的初始值表里,由RW數(shù)據(jù)的初始化程序順手處理好了——說了這么多,如果不好理解,簡單理解成出于優(yōu)化的目的就行了。 要想關(guān)閉這個優(yōu)化,在命令行中加入“--bss_threshold=0”就可以了。順便說下,默認(rèn)設(shè)置相當(dāng)于“--bss_threshold=8”。
.data section 和 .bss section是兩個默認(rèn)的section,你還以定義自己的section,并自己指定將哪些變量放到里面。具體怎么實現(xiàn),請查閱對應(yīng)編譯器的使用手冊。
你可以忘記上面這些,只要記住:變量和代碼都是放在段里面的,段具體放在哪里(什么地址上)則是由 linker 的腳本控制的。
在MDK中(也就是ARM Compiler中),這個腳本叫做scatter-loading file;在IAR和GCC也有對應(yīng)的LinkerScript,只不過語法規(guī)則不同,感興趣的人可以查閱對應(yīng)的手冊,這里只為讀者提供一個自行研究的方向,避免屋上架屋,不再贅述。
> 動態(tài)變量 C語言原生態(tài)支持的動態(tài)變量就只有局部變量了(Local Variable)。理論上說,局部變量只在程序進(jìn)入變量所在的花括號范圍內(nèi)時才從棧(stack)中進(jìn)行分配,一旦程序出了花括號,它的聲明就結(jié)束了——夏蟲不可語冰說的就是局部變量那可憐的一生……
看著新近分配的局部變量,靜態(tài)局部變量深深的吸了一口煙,又長長的吐了出去:時間對它是沒有意義的存在。俗話說“鐵打的花括號,流水的局部變量”,看了太多的生生死死,它已經(jīng)麻木了……然而,命運枷鎖禁錮了靜態(tài)局部變量的腳步,它是多么的向往花括號外面的世界,企盼著有一天一個指針腳踏七彩祥云,將自己拉出牢籠,不再只看著“高墻上四角的天空”……
請從下列成語中選擇出與 “將局部變量的地址傳遞到函數(shù)外部”的做法意義相近的成語: A. 刻舟求劍 B. 刻舟求劍 C. 刻舟求劍 D. 刻舟求劍
與浮萍一般生命短暫、作用范圍有限的局部變量相對,堆(Head)變量是一個奇葩的存在:
首先,堆變量的作用范圍不受花括號限制,但具體在哪個范圍內(nèi)有效,完全由程序邏輯決定(掌握在程序員的手里);
其次,堆變量的生存時間不受花括號限制,但正常情況下都是有限的,指不定什么時候就被Free掉了;少數(shù)比較悲慘的堆變量則滑落到了命運的深淵中,從此被人們所遺忘,陷入了痛苦的永生……
堆變量不是C語言原生態(tài)所支持的變量類型(C++、Java、C#原生態(tài)支持),而是開發(fā)人員通過程序邏輯所構(gòu)造出的特殊變量類型。堆從哪里來呢?我們既可以定義一個很大的數(shù)組(你肯定不會在意數(shù)組的初值對吧),將它交給堆函數(shù)(此時,堆就在ZI里面);也可以告訴編譯器,把 .bss后面的一定RAM區(qū)域化歸特區(qū)——編譯器將不再這個區(qū)域分配靜態(tài)變量——交給堆函數(shù)(此時,堆既不在RW里,也不在ZI里)。堆不是一個聽話的好孩子,經(jīng)常和它的鄰居,ZI、RW和Stack打架。有些嚴(yán)厲的家長為了家族的繁榮和穩(wěn)定,直接就將Heap丟棄了——沒弄清楚它的脾氣之前,你可輕易不要撿回來哦。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!