如何區分 declaration 和 definition

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

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

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

C 和 C++ 都有宣告 declaraction 和定義 definition 的區別 ,本篇文章將探討這兩個的差異。

declaration 宣告讓編譯器知道變數的型別和名稱,以便進行語法檢查;
而 definition 定義則表示該變數已被配置好所需的記憶體空間並可以設定初值。需特別注意, definition 也是一種 declaration 。

關鍵字 extern

需要注意的是,extern 關鍵字可以用來宣告變數或函數,而不定義它們。extern 的作用在於告訴編譯器:

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

從名稱的作用域(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 可以被省略
struct dog {
    int height;
    int weight;
};
class animal;

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

以 linker 角度來看定義式只能有一個

通常,變數的定義應該寫在 .c 檔案中,而不是在 header files 中。如果在 header files 中定義變數,因為 header file 可能會被多個檔案引用,可能會導致定義多個變數的情況,從而導致編譯器報錯。

需要注意的是,一個變數只能被定義一次,但可以聲明多次。這是因為在經過 preprocessor, compiler 和 assembler 處理 source code 後,會生成 seperately relocatable object files。在這些 object file 中,每個變量或函數都被分為 strong symbol 或 weak symbol(可以參考我之前研讀 CS:APP Ch7 時寫的筆記 了解如何分類和 linker 的行為)。由定義式生成並帶有初始值的變量被歸類為 strong symbol,由聲明式生成的變量被歸類為 weak symbol。為了處理所有的可重定位目標文件並生成最終的可執行目標文件, linker 會進行 symbol resolution,以找到所有 weak symbol 對應的 strong symbol。

因此,如果在標頭檔中定義變數,在多個源代碼文件中引用這個標頭檔時,就可能出現多個定義式的情況,這將導致 linker 無法解析 symbol,進而導致編譯器報錯。因此,變數的定義應該寫在 .c 檔案中,而不是在標頭檔中。

以下舉例說明:

[foo1.c]

int x = 122;
void func();

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

[bar1.c]

extern int x;
void func()
{
    x++;
    printf ("x = %d", x);
}
// Output 為 x = 123

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 。

[foo2.c]

int x = 5566;
int main ()
{
}

[bar2.c]

int x = 777;
void f() { }

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

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

Reference

  1. How do I use extern to share variables between source files? - stackoverflow

Updated on 2023-05-13 14:18:42 星期六

在〈如何區分 declaration 和 definition〉中有 1 則留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *