1. 程式人生 > >C語言入坑指南-被遺忘的初始化

C語言入坑指南-被遺忘的初始化


前言

什麼是初始化?為什麼要初始化?靜態變數和區域性變數的初始化又有什麼區別?實際應用中應該怎麼做?本文將一一回答這些問題。

什麼是初始化

初始化指的是對資料物件或者變數賦予初始值。例如:

int value = 8; //宣告整型變數並初始化為8
int arr[] = {1,2,3}; //宣告整型陣列arr,並初始化其值為1,2,3

為什麼要初始化

我們來看一個示例程式。
test0.c程式清單如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    int sum;
    int randNum;
    while(10 > sum)
    {
        randNum =  rand() % 10;

        sum += randNum;
        printf("rand num is %d,sum is %d\n",randNum,sum);
    }
    printf("the final sum is %d\n",sum);
    return 0;
}

程式隨機產生0到9的數字,使得sum的值大於或等於10時,退出程式。
編譯並執行:

gcc  -o test0 test0.c
./test0

執行結果如下(每次執行結果可能不同):

rand num is 3,sum is -4040865
rand num is 6,sum is -4040859
rand num is 7,sum is -4040852
rand num is 5,sum is -4040847
rand num is 3,sum is -4040844
rand num is 5,sum is -4040839
(省略其他內容)

從執行結果來看,程式並沒有達到我們的預期,這是為什麼呢?

很多讀者可能已經知道,問題在於宣告sum之後,沒有為其賦初始值,在這樣的情況下,sum的值是隨機的,因此在一開始sum可能是一個很小的負數,導致多次迴圈出現。很顯然,初始化避免使用了變數的“髒值”。而將sum的宣告改成如下定義即可:

int sum = 0;

如果將sum宣告為靜態變數,情況又會如何呢?

//test1.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    static int sum;
    int randNum;
    while(10 > sum)
    {
        randNum =  rand() % 10;

        sum += randNum;
        printf("rand num is %d,sum is %d\n",randNum,sum);
    }
    printf("the final sum is %d\n",sum);
    return 0;
}

編譯並執行:

rand num is 3,sum is 3
rand num is 6,sum is 9
rand num is 7,sum is 16
the final sum is 16

在這種情況下,程式是能夠符合我們預期的結果,這又是為什麼呢?原因在於靜態變數會被預設初始化。例如,int型別會被初始化為0。那麼問題來了:

  • 為什麼區域性變數未初始化的時候的值是“髒值”?

  • 靜態變數和區域性變數為什麼又不一樣呢?

在解答上面這兩個問題之前,我們需要簡單瞭解一下程式的儲存空間佈局。

程式的儲存空間佈局

C程式主要由以下幾部分組成:

  • 正文段。即機器指令部分,為防止意外被修改,設為只讀。

  • 初始化資料段。它包含了程式中需要明確賦初值的靜態變數。

  • 未初始化資料段。它包含了程式中未賦初值的或初始化為0的靜態變數,在程式開始執行之前,核心將此段中的資料初始化為0。

  • 棧。它儲存了自動(區域性)變數以及函式呼叫所要的資訊。

  • 堆。用於動態記憶體分配。例如使用malloc函式進行記憶體分配。

其中,正文段和資料段的內容是“靜態”的,因為在程式被編譯出來之後,在整個程式地址就確定了,而堆疊中的內容是”動態”變化的,它隨著進行的執行而不斷變化著,再加上棧隨機化的策略,使得程式每次執行時,棧的地址也是不確定的。

區域性變數和靜態變數的初始化有何不同

有了前面的鋪墊,就很好理解兩者的差別了。
未初始化的區域性變數位於棧中,它的位置是不確定的,因此其值也是不確定的。當然,在windows下它的值是0xcccccccc,而“燙”字在MBCS字符集中的值為0xcccccccc,你說巧不巧?

而靜態變數就不一樣的,它的地址是確定的,並且存放在了資料段,而程式在執行之前,未初始化資料段的內容可以很方便地統一被初始化為0。這也就解釋了前面的兩個示例程式的結果為什麼會不一樣。我們加上一些列印,來看一看是否真的如此?

//test2.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    static int sum;
    int randNum;
    while(10 > sum)
    {
        randNum =  rand() % 10;

        sum += randNum;
        printf("rand num is %d,sum is %d\n",randNum,sum);
    }
    printf("the final sum is %d\n",sum);
    printf("sum addr %p,randNum addr %p\n",&sum,&randNum);
    return 0;
}

編譯並執行:

gcc -o test2 test2.c

執行結果1:

rand num is 3,sum is 3
rand num is 6,sum is 9
rand num is 7,sum is 16
the final sum is 16
sum addr 0x60104c,randNum addr 0x7ffd0ea8cf54

執行結果2:

rand num is 3,sum is 3
rand num is 6,sum is 9
rand num is 7,sum is 16
the final sum is 16
sum addr 0x60104c,randNum addr 0x7ffff5e3ddb4

在這裡,sum是靜態區域性變數,而randNun是區域性變數(自動變數),因此可以發現,sum的地址值總是不變的,而randNum的值卻不斷變化著。我們也可以通過nm命令檢視sum的地址:

nm test2 |grep sum
000000000060104c b sum.2805

總結

我們來總結一下本文的主要內容:

  • 如果變數是靜態的,它會被初始化為0;如果變數是自動的,它不會被初始化。

  • 靜態的變數包括全域性變數、靜態全域性變數、靜態區域性變數。

  • 使用區域性變數之前對其進行初始化,避免使用“髒值”。

  • 從可讀性考慮,靜態變數也建議顯示初始化。

  • 初始化為0的靜態變數仍然存在未初始化資料段中(BSS段)。

送幾句熟悉的話給大家:

手持兩把錕斤拷,
口中疾呼燙燙燙。
腳踏千朵屯屯屯,
笑看萬物鍩鍩鍩。

思考

test1.c的程式碼執行結果每次都一樣嗎?為什麼?該如何修改才能使得每次的執行結果不一樣?

棧隨機化的作用是什麼?

推薦閱讀:

C語言入坑指南-陣列之謎

一個命令幫你對文字排序

如何理解 Linux shell中“2>&1”?

推薦一款強大的線上編譯器

Linux常用命令--系統狀態篇

 

關注公眾號【程式設計珠璣】,第一時間獲取更多原創技術文章