# Local & Global Variable Global variable(全域變數)和Local variable(局部變數) 關於C語言的變數,不只是能夠賦值那麼簡單而已,變數還有「生命週期」和「等級」的觀念。 在C語言中,變數的生命週期,也就是變數的有效範圍(scope),可分為「全域」和「局部」兩種。 我們先來講解變數的有效範圍(scope),先看看底下幾條規則: Global variable(全域變數)和Local variable(局部變數)的規則: 1. Global variable(全域變數)是在{ }區塊範圍「之外」宣告的變數,因此對於Global variable來說,它的生命週期不受{ }的範圍限制。 2. 相對的,Local variable(局部變數)就是在{ }的範圍內宣告的變數,只要一離開這個{ }的範圍,它的「生命」就會消失,不再有效。也就是說,一旦脫離了Local variable被宣告的{ }範圍,就無法再存 / 取它。 3. Global variable和Local variable的觀念是「相對」的,也就是說可能對於某個{ }區塊來說它是Global variable,但如果它仍被包在更大的{ }區塊之內,以這個更大的{ }的觀點來看,它仍然是Local variable。 4. 當出現相同名稱的變數時,編譯器會優先取用Local variable,而不是Global variable。可以想成,若就近就可以取得的變數,就不需要再跑出去找了。 5. 除非是{ }範圍內找不到對應的Local variable的宣告,編譯器才會脫離該{ }區塊,去找尋更大的{ }範圍內有無對應的宣告。若找的到,則該變數對於該{ }區塊而言,就會成為Global variable;若都找不到,則編譯器會跳出「該變數未宣告」的錯誤訊息。 6. 對於Local variable來說,由於有效範圍的原因,即使是同樣名稱的變數,只要它們位於不同的{ }區塊,就沒有重複宣告的問題,當然你也可以把不同{ }區塊的Local variable都設為不同的名稱,可能會更加容易區別(非必要);但Global variable則仍然會有重複宣告的問題存在。 說了那麼多,來看個實際的範例: ![](https://i.imgur.com/M0ZQOcd.png) 注意我們在第1列、第2列,也就是整個程式的{ }區塊之外,宣告y、z為整數型態,沒有比它更大的{ }範圍了,所以對於整個程式來說,y、z即為Global variable,即圖中整個藍底區塊都是y、z的有效範圍。 我們來逐步解析這段程式碼: 一、 程式從進入點開始執行,也就是main()函數的{ }區塊,即標示為第9列的地方開始執行。 二、 第10 ~ 12列以指派運算子「=」進行賦值,但編譯器只有在第9列找到變數x是個被宣告為整數型態的Local variable,卻不認識y和z,於是跳脫這個{ }區塊出去找y和z到底是什麼,並順利在第1列和第2列找到了被宣告為Global variable的y和z。也就是說,我們在第11列和第12列,是替做為Global variable的y、z賦值。 三、 因此,在第13列,要求印出三者的值時,就印出x(Local)和y、z(Global)三者的值都是10。 四、 程式繼續執行到第14列,要求呼叫myfunction()函數並代入引數值5,所以程式跳回到第3列的位置,並把a = 5代進去,此時在myfunction()函數的{ }區塊內,x、y、z都被賦值為5。 五、 不過,由於Local variable的觀念,我們知道在第4列和第5列被宣告為Local variable的x、z兩個整數變數,它們的有效範圍只存在於myfunction()函數的{ }區塊之內,一旦脫離就會消失,只有整數變數y仍然保持著它Global variable的身分,y的有效範圍為整個程式(藍底區塊),所以myfunction()函數改變了變數y的值。 六、 因此,在第15列,要求印出現在三者的值,x、z的值仍保持不變,只有y的值受到改變。 其實這個範例的寫法是非常差勁的,因為如果沒有仔細推敲過,很難一眼就知道輸出的結果,只是用來讓讀者了解觀念而已。由這個例子也可以看出,一旦使用了Global variable,雖然電腦不會搞錯,但對人類來說,卻徒增複雜度,讓程式碼變得難懂,以至於更容易犯錯,也提高了未來維護和修改的難度,所以大多數老師或教學都會請學生盡量少使用Global variable,或是乾脆都不要使用,原因就是如此。 然而,在少數情況下還是適合使用Global variable的。例如:可以通過Global variable的使用,來避免常用變數在一系列函數間的頻繁傳遞,以提高運算效能。不過還是建議不那麼熟悉的人,盡量少用Global variable。 C語言對於Global variable的使用沒有任何限制,但在大多數「物件導向」的程式語言,例如Java或C#,則規定所有的程式碼都必須被放在類別或方法的{ }區塊中,就能減少很多這方面的困擾。 ----------------------------------------------------------------------------------------- 變數的等級: 接續剛才全域變數和局部變數的觀念,再來談談變數的「等級」。 C語言預設的變數等級是auto(自動),也就是當變數所在的函數被呼叫(執行)時,系統才會配置記憶體空間存放函數所宣告的變數值,並且在函數結束時自動釋放掉變數所佔用的記憶體空間。當下次再度呼叫函數時,系統又會再一次重新配置記憶體空間給函數內的變數。 是否覺得這段文字很熟悉,沒錯,這就是Local variable的觀念,系統不會一直把變數的值保留在記憶體中,而是不斷重複配置 – 釋放 – 配置 – 釋放 … 的動作。由於auto等級的變數是系統預設的等級,所以我們不需要特別告訴系統要使用它。 除了auto等級的變數,C語言還提供了static(靜態)等級的變數。被宣告為static等級的變數,在其函數結束時,仍然不會自動釋放掉它所佔用的記憶體空間,而是繼續霸佔著不放,於是在{ }區塊之外也能存 / 取static等級的變數。這就是Global variable的觀念了,也就是說一旦我們宣告了一個Global variable,也就相當於把這個變數宣告為static(靜態)的。由於static等級不是預設等級,因此需要特別在宣告的最前面寫上「static」修飾,就像形容詞一樣。 我們來看一段實際的程式碼: void function1() // 定義函數function1() { int a = 0; // 沒有特別註明,即代表宣告a為auto等級的整數變數 a++ // a = a + 1 printf(”%d\n”,a); } void function2() // 定義函數function2() { static int a = 0; // 宣告a為static等級的整數變數 a++ // a = a + 1 printf(”%d\n”,a); } main() // 程式的進入點 { int i; // C語言無法在迴圈的()內才宣告變數 printf(”Execute function1 three times:\n”); for(i=0 ; i<3 ; i++) function1(); // 呼叫(執行)function1()函數3次 printf(”Execute function2 three times:\n”); for(i=0 ; i<3 ; i++) function2(); // 呼叫(執行)function2()函數3次 } 執行結果: 一、呼叫function1()函數3次,每次都印出1,原因是function1()函數的變數a為auto等級,所以它的值每一次都被重新分配 – 釋放 – 分配 – 釋放 …。 二、呼叫function2()函數3次,分別印出「1、2、3」,發現輸出值是會持續被累加的,原因是function2()函數的變數a為static等級,每次執行完函數都不會把變數占用的記憶體空間釋放出來,所以變數a的值當然就會不斷累加上去。 三、也因為static等級的變數不會在執行完一段敘述之後釋放出記憶體空間,相當於任何人都可以存 / 取這段記憶體空間的資料,也就是「共享」的概念。雖然在某些特殊的場合很實用,但也會造成資訊安全的疑慮,因為駭客可利用這個特性,竊取static等級的變數的資料。用比喻來說,就是你的車子每次開完都不停回自家車位,而是停在大馬路邊,當然會提高失竊的風險。 除了auto和static等級之外,C語言還提供了兩個變數等級:register和extern。 我們先解說register等級,要理解為什麼會有register等級,還要了解電腦記憶體的硬體架構。簡單的說,register也是一段記憶體空間(台灣翻譯為暫存器),不過由於它是內建在CPU的記憶體區塊,距離CPU最近,所以CPU做存 / 取的速度也會是最快的。所以,宣告為register等級的目的只有一個:讓變數的存 / 取(或說讀 / 寫)速度達到最快,以執行某些需要高速運算的程式。不過CPU的暫存器空間都不大,所以只能讓少許被密集使用的變數被宣告為register等級。 例如以下的程式碼,將變數sum和i宣告為register等級,讓CPU可以用最短的距離存 / 取記憶體,因此執行速度會超快。 register int sum = 0 ; register int i ; for( i = 1 ; i<=100 ; i++) sum = sum + i ; 不過,由於現代電腦效能的飛速發展,早已和C語言被發明的年代不可同日而語,所以效能的差異並不明顯,甚至幾乎沒有感覺,因此一般程式設計師很少自行指定register等級的變數。 還有最後一個extern(外部的)等級,它比較特別,是專門用來修飾全域變數(Global variable)的關鍵字。它的用途是,當所要存取的全域變數的有效範圍並不涵蓋到該{ }區塊時,必須要用extern做修飾,告訴系統要去{ }的範圍之外找尋。 我們來看個例子: main() { int i ; sum = 0 ; // 注意sum沒有宣告就賦值了 for(i=1 ; i<=100 ; i++) sum = sum + i ; } int sum ; // 注意這邊把sum的宣告擺在全域變數 由於sum是在main()函數的{ }範圍外被宣告的,所以sum對於main()函數來說是全域變數,這沒有問題。但由於「int sum ;」這段宣告寫在main()函數的{ }區塊下方,而不是正規寫法的上方,由於變數的有效範圍是從宣告處起「往下」,因此「int sum ;」的有效範圍並不包含main()函數,所以編譯程式時會發生:「變數sum未宣告」的錯誤。 以下是正確的程式碼: main() { int i ; extern int sum ; for(i=1 ; i<=100 ; i++) sum = sum + i ; } int sum ; 說實在,由於不鼓勵使用Global variable,而且上面這樣的寫法也比較怪異(雖然沒錯,但不是正規習慣的寫法),所以extern幾乎不會用到,也建議少用為妙。