宣告 告訴編譯器「這個名字存在,類型是什麼」;定義 告訴編譯器「配置記憶體,這裡就是實體」。搞混兩者會導致連結錯誤、多重定義、未定義引用等經典問題。本文徹底釐清三種寫法的本質差異。
| 程式碼 | 性質 | 記憶體配置 | 連結屬性 | 可重複 |
|---|---|---|---|---|
int i; | 暫定定義 | 可能 (視編譯器/連結器) | 外部連結 | 同編譯單元不可,跨檔案可 (合併) |
int i = 0; | 定義 (含初始化) | 是 | 外部連結 | 不可 (重複定義錯誤) |
extern int i; | 純宣告 | 否 | 外部連結 | 可 (多處宣告同一實體) |
int i; — 暫定定義int i = 0; 存在,int i; 視為宣告extern int i; 或 int i = 0;int i = 0; — 定義redefinition of 'i'extern int i; — 宣告undefined reference to 'i'#include <stdio.h>
int global_counter = 0; // 定義:配置記憶體、初始化
void increment() {
global_counter++;
}
extern int global_counter; // 宣告:不配置記憶體
void print_counter() {
printf("Counter: %d\n", global_counter);
}
gcc file1.c file2.c -o prog
# 正常:一個定義 + 一個宣告
// a.c
int x = 1;
// b.c
int x = 2; // ❌ 重複定義
// 編譯
gcc a.c b.c
# a.c: x defined here
# b.c: redefinition of 'x'
// a.c
extern int y; // 只有宣告
// b.c
extern int y; // 只有宣告
// 編譯
gcc a.c b.c
# undefined reference to 'y'
// a.c
static int hidden = 42; // 內部連結,僅 a.c 可見
// b.c
extern int hidden; // ❌ 找不到:static 變數無外部連結
void foo(); — 宣告 (原型)void foo() { ... } — 定義static void foo() — 內部連結,僅本檔可見
// header.h
inline int shared = 0; // 允許多檔案包含,連結器合併為一個實體
// 替代傳統「.c 定義 + .h extern 宣告」模式
extern int g_var;、void func();int g_var = 0;、void func() { ... }「宣告不配置記憶體,定義配置記憶體」是核心鐵律。extern 明確表達「這只是宣告」,初始化 強制成為定義,無初始化 則是暫定定義 (C 特有)。理解此三者差異,配合「頭檔宣告、源檔定義」規範,即能徹底避免連結階段的「未定義引用」與「重複定義」噩夢。