| 作用域類型 | 宣告位置 | 可見範圍 | 生命週期 | 典型用途 |
|---|---|---|---|---|
| 區塊/區域 | 函數內部區塊 { ... } | 從宣告點至區塊結尾 } | 自動:進入區塊建立,離開銷毀 靜態:程式啟動建立,結束銷毀 | 迴圈計數器、暫時變數、函數參數 |
| 檔案/全域 | 所有函數外部 (編譯單元最上層) | 整個編譯單元 (.c + 所有 #include);static 限定本檔,extern 暴露給連結器 | 程式啟動至結束 | 模組狀態、設定參數、單例、回呼表 |
| 函數原型 | 函數參數列表 | 函數本體內 | 同區域自動 | 參數傳遞 |
auto — 隱含預設,極少顯式使用
void f() {
auto int x = 10; // 等同 int x = 10;
// C23: auto x = 10; // 型別推導 (如 C++ auto)
}
register — 請求寄存器分配,現代編譯器多忽略
void sum(int n) {
register int i; // 提示編譯器盡量用寄存器
for (i=0; i
&i 編譯錯誤)、不能是陣列/結構體、僅限標量型別。register 關鍵字僅作為提示,實際是否落入寄存器由最佳化器決定 (-O2 以上通常自動完成)。static — 雙重語意,視宣告位置而定| 位置 | 作用域 | 連結屬性 | 儲存期 | 初始化 |
|---|---|---|---|---|
| 檔案層級 (全域) | 檔案作用域 | 內部連結 (internal linkage),連結器不可見 | 靜態 (程式全程) | 編譯期常數或零初始化 |
| 區塊層級 (區域) | 區塊作用域 | 無連結屬性 | 靜態 (程式全程) | 僅首次執行初始化,之後保持值 |
static 經典用法:函數內狀態保持
int Counter() {
static int count = 0; // 僅初始化一次
return ++count;
}
// 呼叫 3 次回傳 1, 2, 3
// 避免全域變數污染命名空間,實現「模組私有狀態」
static:資訊隱藏
// utils.c
static int internal_cache[1024]; // 僅本檔可見
static void flush_cache() { ... } // 僅本檔可呼叫
void Public_API() { flush_cache(); } // 對外介面
extern — 宣告非定義,跨檔案共享
// a.c
int global_counter = 0; // 定義 (外部連結)
void Increment() { global_counter++; }
// b.c
extern int global_counter; // 宣告,告訴編譯器「別處有定義」
extern void Increment(); // 函數 extern 可省略
void UseIt() { printf("%d\n", global_counter); Increment(); }
extern 只宣告,不配置記憶體;定義只能出現一次 (One Definition Rule)。extern "C" 告訴 C++ 編譯器用 C 命名慣例 (無 name mangling),使 C++ 可呼叫 C 函數、存取 C 變數。static 初始化競爭 (C11 解決)
// C11 保證執行緒安全初始化 (magic statics)
int GetID() {
static _Atomic int id = 0; // 或配合 call_once
return atomic_fetch_add_explicit(&id, 1, memory_order_relaxed);
}
C11 規定區域 static 初始化為原子操作,無需額外鎖。
const 與儲存類別的關係const int x = 5; — 檔案層級預設 內部連結 (如 static),若需跨檔共享需加 extern const int x; 定義端與宣告端同加 extern。static const — 明確內部連結,編譯器常傳播常數值,不佔用資料段空間。for 迴圈變數 (C99)
for (int i = 0; i < n; i++) { ... } // i 僅在 for 區塊可見
// C89 需在函數開頭宣告
auto (不寫關鍵字),盡量縮小作用域 (C99 混合宣告)。static 區域變數 (單一函數) 或 檔案層級 static (模組私有)。extern 定義於一 .c,.h 以 extern 宣告。static),否則每個包含檔各自擁有副本。static const 或 enum,避免 #define 宏污染命名空間。_Atomic / mutex / thread_local (C11 _Thread_local),勿用普通 static。.h 中 int g_val = 0; → 多重定義連結錯誤或各檔獨立副本。static 陣列指標 → 呼叫者以為擁有獨立副本,實則共享同一記憶體。extern int x; 在多個 .c 皆定義 int x; → 重複定義連結錯誤。register 強制使用寄存器,實際被忽略時效能無提升。const 檔案層級預設內部連結,導致跨檔 extern const 連結失敗。| 特性 | C89 | C99 | C11 | C23 |
|---|---|---|---|---|
| 混合宣告/程式碼 | ❌ | ✅ | ✅ | ✅ |
for (int i...) | ❌ | ✅ | ✅ | ✅ |
_Thread_local | ❌ | ❌ | ✅ | ✅ |
_Atomic | ❌ | ❌ | ✅ | ✅ |
區域 static 執行緒安全初始化 | 未定義 | 未定義 | ✅ | ✅ |
auto 型別推導 | ❌ | ❌ | ❌ | ✅ |
作用域與儲存類別是 C 語言「模組化、封裝、並發安全」的地基。精通 static 的雙重語意、extern 的跨檔契約、register 的歷史包袱、_Thread_local/_Atomic 的現代並發原語,能讓你寫出介面乾淨、無符號衝突、執行緒安全、易於測試與重構的 C 程式碼。下次宣告變數時,先問三個問題:誰能看見?活多久?誰負責銷毀? — 答案自然導向正確的儲存類別選擇。