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
Updated on 2023-05-13 14:18:42 星期六
在〈如何區分 declaration 和 definition〉中有 1 則留言