如何區分 declaration 和 definition

C 和 C++ 都有所謂的宣告 declaraction 和定義 definition 的區別 ,這篇文章就想要把這兩個的差別探討清楚。

declaration,讓編譯器知道變數的型別和名稱,好讓編譯器可以做語法檢查。

definition ,一旦寫出定義式,該變數將被配置好需要的記憶體空間,同時可以為其設定初值。而 definition 也是一種 declaration 。

C 和 C++ 都有所謂的宣告 declaraction 和定義 definition 的區別 ,這篇文章就想要把這兩個的差別探討清楚。

declaration,讓編譯器知道變數的型別和名稱,好讓編譯器可以做語法檢查。

definition ,一旦寫出定義式,該變數將被配置好需要的記憶體空間,同時可以為其設定初值。而 definition 也是一種 declaration 。

關鍵字 extern

需要留意的是 extern 也可以用來宣告一個變數而不定義它,extern 的功用在於告訴 compiler

這個變數或函式可能在別的文件定義,我現在告訴你它的型別。好讓你進行語法檢查,請不要說我未宣告就使用了!

從名稱的作用域(scope)角度來看,一個 global variable 的作用域會限定在它被定義的那個 source code 內,透過 extern 關鍵字,就能擴展一個 global variable 的 scope ,使得在其他檔案中也能使用該 global variable 。

使用 extern 關鍵字,但你又給該變數設定初值的話,那這個宣告式將被當成定義式看待,必須注意定義式只能存在一個,但是宣告可以有很多個。

extern double pi = 3.1415;
double pi;  // 定義第二次了, compiler 會報錯
extern double pi = 3.1415;
extern double pi;  // 這是宣告,沒有問題

以下舉一些例子

// declaration
extern int i;
extern int foo(int, int)
double bar (float);  // extern 用在 function declaration 可以被省略
class animal;

// definition
int bar;
int i = 1;
int func (int val) { return val+1; }
class animal { };

定義式只能有一個

正常都會在 .c 檔內定義變數,不會在 header file 裡面定義變數,因為如果放到 header file 內,由於 header file 可能會被多個檔案 include ,將造成定義式不只一個的狀況,編譯器就會報錯。但其實我們還是會常在 header file 裡面看到 macro 或 struct 的定義,那種狀況會使用 header guard (#define guard)來保護,但那並不是這篇文章想探討的。

前面提到,定義式只能有一個,而宣告式可以有多個,關於這點,可以用 compiler 和 linker (連結器) 的角度來解釋定義式超過一個為什麼是非法的。

source code 在經過 preprocessor, compiler 和 assembler 處理後,產生 seperately relocatable object files ,在這些 files 內,每一個 variable 或 function 會被分類為 strong 或 weak symbol (可以參考我之前研讀 CS:APP Ch7 時寫的筆記 了解如何分類和 linker 的行為),經由定義式產生並帶有初值的會被歸類為 strong symbol ,經由宣告式產生的會被歸類為 weak symbol ,為了處理所有的 seperately reloctable object files 並產生一個最終的 executable object file ,linker 會進行 symbol resolution ,替所有的 weak symbol 都找到一個 strong symbol 來 reference ,以下舉例說明:

[foo1.c]

int x = 5566;
int main()
{
    func ();
    printf ("%d", x);
}

[bar1.c]

extern int x;
void func()
{
    x++;
}

foo1.c 跟 bar1.c 會各自被轉換為 relocatable object file ,當要產生最終的 executable object file , foo1.c 內的變數 x 是一個 strong symbol ,而 bar1.c 內的變數 x 是一個 weak symbol ,weak symbol 會被認定需要 reference 到 strong symbol ,每個 strong symbol 會被分配一個執行時的唯一 address 。

[foo2.c]

int x = 5566;
int main ()
{
}

[bar2.c]

int x = 777;
void f() { }

foo2.c 和 bar2.c 的變數 x 均為 strong symbol ,linker 會報出錯誤,因為有兩個 strong symbol 同名, linker 無法做 symbol resolution ,會報出錯誤。

從 linker 的角度,如果有多個定義式,將使得 symbol resolution 無法繼續下去,最終的 executable object file 也無法被順利產出。