變數範圍


在 C 中,談到變數範圍(scope)涉及許多層次,可以談到很複雜,這邊先談談全域變數(Global variable)、區域變數(Local variable)與區塊變數(Block variable)。

全域變數是指直接宣告在(主)函式之外的變數,這個變數在整個程式之中都可見,例如:

const double PI = 3.14159;

doule area(double r) {
    return r * r * PI;
}

int main(void) {
    // .....
    return 0;
}

在這個例子中,PI 這個變數可以在主函式 main 與函式 area 使用,全域變數最好只用來定義一些常數,或者是確實具有全域概念的變數,不應為了方便而草率地將變數設為全域變數,否則會發生名稱空間重疊等問題;全域變數的生命週期始於程式開始,終止於程式結束。

區域變數是指函式中宣告的變數,或是宣告在參數列的參數,範圍只在函式之內,例如在上例的 main 函式無法取用 area 函式的變數 r,區域變數的生命週期始於函式執行,終止於函式執行完畢。

區塊變數是指宣告在某陳述區塊中的變數,例如 while 迴圈區塊,或是 for 迴圈區塊,例如下面的變數 i 在迴圈結束之後,就不再有效:

for(int i = 0; i < 100; i++) {
    // ....
}

範圍大的變數與範圍小的變數同名狀況時,範圍小的變數會暫時覆蓋範圍大的變數,稱為變數覆蓋,例如:

int i = 10;
for(int i = 0; i < 100; i++)  {
    // ...
}
printf("%d\n", i);

這個程式最後顯示的 i 值是 10,執行迴圈時,迴圈中的 i 變數範圍覆蓋迴圈外的 i 變數;全域變數與區域變數同名時也是如此運作。

再來介紹 static,這個關鍵字有兩種不同的概念,記憶體模式與連結的方式,依使用的場合而有所不同。

就記憶體模式而言,變數宣告時若加上 static,執行時期會一直存在記憶體的固定位置,在不同 .c 檔案頂層定義的變數,即使沒有加上 static,也是這種記憶體模式。

因此若在函式中宣告 static 變數,代表著就算函式執行完畢,變數也不會消失。例如:

#include <stdio.h>

void count(); 

int main(void) { 
    for(int i = 0; i < 10; i++) {
        count(); 
    }

    return 0; 
} 

void count() { 
    static int c = 1; 
    printf("%d\n", c); 
    c++; 
}

執行結果:

1
2
3
4
5
6
7
8
9
10

雖然變數 c 是在 count 函式宣告,但是函式結束後,變數仍然有效,直到程式執行結束時才消失,雖然變數一直存在,但由於範圍限於函式之中,函式外仍無法存取該變數。

就連結模式而言,得先來探討一下,定義在另一個 .c 檔案頂層範圍中的變數,可以直接拿來用嗎?預設是不可以的,然而,可以透過 extern 聲明變數會在其他地方定義,例如:

foo.c

double v = 1000;
// ... 其他定義

main.c

#include <stdio.h>

int main() { 
    extern double v;
    printf("%f\n", v);

    return 0; 
}

在 main.c 中並沒有宣告 v,只是以 extern 聲明 v 是在其他地方定義,編譯器會試著找出符合的 v,結果在 foo.c 找到,因而會顯示結果為 1000,要注意的是,extern 聲明 v 在其他位置定義,因此不能與初始式使用。

#include <stdio.h>

int main() {
    extern double v = 2000; // error, `v' has both `extern' and initializer
    // ...
    return 0;
}

若要設定 v 變數,必須在 extern 聲明之後:

#include <stdio.h>

int main() {
    extern double v;
    v = 2000; 
    // ...

    return 0;
}

定義在 .c 檔案的函式,若非實作標頭檔中定義的函式原型,預設是不能在另一個 .c 中使用的,若要使用得用 extern 宣告,例如,若 a.c 中定義了 void foo() {...},main.c 要使用,必須用 extern 如下宣告:

extern void foo();

定義在一個 .c 中的名稱,可以只改為內部連結,也就是想表示它只用在該 .c 中,這時可以加上 static,例如,若方才的 foo.c 中的 v 宣告為:

foo.c

static double v = 1000;
// ... 其他定義

那麼範疇就只侷限在 foo.c 中了,不會被 extern 拿來連結。

函式若使用 static 修飾,表示內部連結,不會被 extern 拿來連結,如果想將函式實作定義在 .h 檔案中,可以加上 static 修飾。例如:

// 定義在 .h 中
static void foo() {
    ...
}