說了這麼多空的,我們來看一個例子,下面的程式碼非常簡單,就是在 test 函式中定一個了一個區域性 int 型變數 i,然後打印出它的值,再賦值為 321,然後在 main 函式中呼叫 它兩次。
在編譯執行前,根據我們的理論,區域性變數如果沒有初始化,那它的值是未知的,因此第一個 test 呼叫,會打印出隨機的一個數。雖然第一個 test 函式在最後對區域性變數 i 賦值了 321,但是在它返回後,就被釋放了。因此第二次呼叫 test 時,輸出應該還是一個隨機數。那對不對呢?編譯執行,發現程式輸出
134567128321
出乎意料,第二次打印出的值正是第一個 test 末尾賦的值 321。有一種初學者是這樣,原本就沒有把這條語法規則記牢,或者對自己的記憶力沒信心,看到這個結果就會想:哦那肯定是我記錯了,改過來記吧,應該是“函式中的區域性變數具有一直存在的固定的儲存空間,每次函式呼叫時使用它,返回時也不釋放,再次呼叫函式時它應該還能保持上次的值”。
這個問題我正好整理過:
在回答這個問題之前,請看下面這張非常經典的圖:這個圖就是程式在執行所需的記憶體佈局。簡單來說,就是程式在執行時會佔用記憶體,佔用的記憶體每個區域用途都是不同的,有的區域用做堆區,有的用做棧區,等等。
為什麼函式返回時,區域性變數就不能用了
程式每呼叫一個函式,系統就自動在棧區劃分一塊區域給該函式使用,函式內部定義的區域性變數,也存在此處。因為並不能知道系統分配的棧區原來填充的是什麼樣的資料,所以如果函式內部定義的區域性變數沒有初始化(沒有賦初值)就使用它,它的值也是未知的。
當函式執行完畢,返回時,系統將收回這塊分配的棧區,所以函式的區域性變數的值就不能繼續使用了。
說了這麼多空的,我們來看一個例子,下面的程式碼非常簡單,就是在 test 函式中定一個了一個區域性 int 型變數 i,然後打印出它的值,再賦值為 321,然後在 main 函式中呼叫 它兩次。
在編譯執行前,根據我們的理論,區域性變數如果沒有初始化,那它的值是未知的,因此第一個 test 呼叫,會打印出隨機的一個數。雖然第一個 test 函式在最後對區域性變數 i 賦值了 321,但是在它返回後,就被釋放了。因此第二次呼叫 test 時,輸出應該還是一個隨機數。那對不對呢?編譯執行,發現程式輸出
134567128321出乎意料,第二次打印出的值正是第一個 test 末尾賦的值 321。有一種初學者是這樣,原本就沒有把這條語法規則記牢,或者對自己的記憶力沒信心,看到這個結果就會想:哦那肯定是我記錯了,改過來記吧,應該是“函式中的區域性變數具有一直存在的固定的儲存空間,每次函式呼叫時使用它,返回時也不釋放,再次呼叫函式時它應該還能保持上次的值”。
還有一種初學者是懷疑論者或不可知論者,看到這個結果就會想:教材上明明說“區域性變數的儲存空間在每次函式呼叫時分配,在函式返回時釋放”,那一定是教材寫錯了,教材也是人寫的,是人寫的就難免出錯,哦,連C99也這麼寫的啊,C99也是人寫的,也難免出錯,或者C99也許沒錯,但是反正執行結果就是錯了,計算機這東西真靠不住,太容易受電磁干擾和宇宙射線影響了,我的程式寫得再正確也有可能被幹擾得不能正確執行。
這兩種想法當然不對。我們說,因為並不能知道系統分配的棧區原來填充的是什麼樣的資料,所以如果函式內部定義的區域性變數沒有初始化(沒有賦初值)就使用它,這個未知的值可以是隨機的,那既然是隨機值,它為什麼不可以恰好是 321 呢?
函式返回後,系統收回原分配的棧區,卻不一定會去清零它。如果下一次,恰好又把這塊棧區分配給 test 函式,區域性變數 i 所在的區域恰好是上一次 i 所在的區域,而上次 test 函式返回之前,把這塊區域賦值為 321 了,那這次 i 的“未知值”就正好是 321 也就不奇怪了,對吧。
為了驗證我們上面的解答,我們在兩個 test 之間加一行 printf,再做次實驗:
再編譯執行,發現輸出為
134567128看到沒,第一個 test 函式末尾賦的值,並不能保證一定傳給下一次 test。
未初始化的區域性變數的值是不確定的,上一次誰使用過這一塊的棧區,恰好留在區域性變數所在區域的值等於幾,這次區域性變數的未初始化的預設值就是幾。
全域性變數為什麼可以一直保留到程式結束?
這是因為,全域性變數要麼儲存在 bss 段,要麼儲存在 data 段,這兩個段會一直保留到程式結束。
未初始化的全域性變數儲存在 bss 段,已初始化的全域性變數儲存在 data 段。