static 是 C/C++ 中最具多重語意的關鍵字,根據出現位置不同,分別代表:區域靜態變數、檔案級靜態變數/函式、類別靜態成員。本文系統梳理三大用法、記憶體佈局、初始化語意、多執行緒安全性陷阱與最佳實踐。
void Counter() {
static int count = 0; // 僅初始化一次,儲存在靜態儲存區
printf("Call %d\n", ++count);
}
int main() { Counter(); Counter(); Counter(); } // 輸出 1,2,3
| 特性 | auto (區域) | static (區域) |
|---|---|---|
| 儲存區 | 堆疊 | 靜態資料區 |
| 生命週期 | 函式呼叫期間 | 程式整個執行期間 |
| 初始化時機 | 每次進入函式 | 首次執行到該行(C++11 保證執行緒安全) |
| 預設值 | 不定值 | 0 / nullptr |
| 作用域 | 區塊內 | 區塊內 |
const char* IpToStr(uint32_t ip) {
static char buf[16]; // 靜態緩衝區,回傳指標有效
const uint8_t* p = (const uint8_t*)&ip;
sprintf(buf, "%u.%u.%u.%u", p[0], p[1], p[2], p[3]);
return buf;
}
所有呼叫共用同一塊記憶體。執行緒 A 呼叫中被切換,執行緒 B 覆寫緩衝區,A 繼續執行時讀到 B 的資料。
// 危險範例
const char* IpToStr(uint32_t ip) {
static char buf[16]; // 共用緩衝區
// ... sprintf ...
return buf; // 多執行緒下資料競爭
}
解決方案:
void IpToStr(uint32_t ip, char* buf, size_t len)。thread_local (C++11) 使每個執行緒擁有獨立副本。std::string 由呼叫端管理生命週期。
// file1.cpp
static int g_fileVar = 10; // 僅 file1 可見
static void InternalFunc() { ... } // 僅 file1 可呼叫
int g_sharedVar = 20; // 外部連結,其他檔可 extern
void ExportedFunc() { ... }
namespace { int x; void f(){} },語意更清晰,支援類別與樣板。
class Logger {
static int instanceCount; // 宣告(類別內)
static void Init(); // 靜態成員函式
public:
Logger() { ++instanceCount; }
static int GetCount() { return instanceCount; }
};
int Logger::instanceCount = 0; // 定義(全域/命名空間範圍,單獨編譯單元)
void Logger::Init() { ... }
this 指標,僅能存取靜態成員。private static 僅類別內可存取。+------------------+ | 程式碼區 | 函式機器碼、唯讀常數 +------------------+ | 靜態資料區 | 全域/靜態變數(含 static 區域、static 成員) | (.data/.bss) | 程式啟動配置,結束釋放 +------------------+ | 堆積 | new/malloc 動態配置 +------------------+ | 堆疊 | auto 區域變數、函式呼叫幀 +------------------+
static 僅用於單執行緒、非遞迴、生命週期需跨呼叫的簡單場景。thread_local、呼叫端緩衝區、或 std::call_once + static 實現執行緒安全單例。namespace { ... }(C++)。.cpp,頭檔僅宣告。