跨檔案共用變數是大型專案常見需求,但 extern、static、const 在全域變數上的語意差異極易導致連結錯誤(LNK2005 重複定義、LNK2001 未解析符號)。本文從編譯單元、聲明與定義、連結模型三個基礎概念切入,系統釐清三者用法與最佳實踐。
.c/.cpp 搭配其引用的頭檔,編譯產出一個 .obj。專案由多個編譯單元組成,連結器將其合併為 EXE/DLL。extern 代表外部連結(跨單元可見);static 代表內部連結(僅本單元可見);const 在 C++ 預設內部連結,C 則為外部連結。extern:跨檔案共用全域變數的標準作法頭檔 global.h(僅聲明)
#ifndef GLOBAL_H
#define GLOBAL_H
extern int g_counter; // 聲明:不配置記憶體
extern void InitCounter(); // 函式聲明
#endif
實作檔 global.cpp(定義)
#include "global.h"
int g_counter = 0; // 定義:配置記憶體,初始化
void InitCounter() { g_counter = 100; }
使用檔 main.cpp
#include "global.h"
int main() { InitCounter(); printf("%d", g_counter); return 0; }
✅ 正確:global.cpp 定義一次,其他單元透過 extern 引用同一記憶體位址。
// global.h 錯誤示範
extern int g_counter = 0; // 等同於定義!多檔包含會導致 LNK2005 重複定義
解決:頭檔只寫 extern int g_counter;,定義放在唯一的 .cpp。
static:檔案級私有全域變數static 與 extern 互斥,不可同時修飾同一符號。.cpp 可定義同名 static int g_x;,互不干擾。
// moduleA.cpp
static int g_mode = 1; // 僅 moduleA 可見
void FuncA() { g_mode = 2; }
// moduleB.cpp
static int g_mode = 100; // 獨立的另一個變數,無衝突
void FuncB() { g_mode = 200; }
適用場景:模組內部狀態、快取、單例實作細節隱藏。
const:常數與連結屬性的語言差異| 語言 | 全域 const 預設連結 | 跨檔共用做法 |
|---|---|---|
| C | 外部連結(同 extern) | 頭檔 extern const int MAX=100;,.c 定義 const int MAX=100; |
| C++ | 內部連結(同 static) | 頭檔 extern const int MAX=100;(強制外部),.cpp 定義 const int MAX=100; |
C++ 最佳實踐:
// constants.h
#pragma once
extern const int MAX_CONN; // 聲明,外部連結
extern const char* APP_NAME; // 指標常數
// constants.cpp
#include "constants.h"
const int MAX_CONN = 1024;
const char* APP_NAME = "MyApp";
| 修飾符 | 連結屬性 | 作用域 | 記憶體配置 | 典型用途 |
|---|---|---|---|---|
extern | 外部 | 全專案(含頭檔聲明處) | 定義處配置一次 | 跨模組共用狀態、介面函式 |
static | 內部 | 僅定義所在編譯單元 | 定義處配置一次 | 模組私有狀態、避免符號污染 |
const (C) | 外部 | 全專案 | 定義處配置(唯讀段) | 跨檔常數、唯讀設定 |
const (C++) | 內部 | 僅定義單元(除非加 extern) | 定義處配置(唯讀段) | 編譯期常數、模板參數、檔案級常數 |
extern 聲明(或 extern const),絕不 放定義(含初始化)。static,減少連結器符號表壓力。extern 於頭檔,.cpp 定義一次。namespace { int x; } 替代 static(C++ 推薦,語意更清晰)。extern 拼寫錯誤。