# 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則仍然會有重複宣告的問題存在。 說了那麼多,來看個實際的範例:  注意我們在第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幾乎不會用到,也建議少用為妙。
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.