Copy-On-Write (COW) 是經典 STL std::string 實現的核心優化:多個 string 共享同一緩衝區,直到有修改操作才真正拷貝。本文從原理、實現、觸發條件到現代棄用原因全景解析。
「不做直到必須做」。構造/賦值時共享緩衝區,引用計數 +1;修改時檢查引用計數 > 1 則拷貝新緩衝區,原計數 -1。
string s1 = "hello world";
string s2 = s1; // 共享內存
printf("%p %p\n", s1.c_str(), s2.c_str()); // 相同地址
s1[1] = 'q'; // 觸發 COW
s2[1] = 'w';
printf("%p %p\n", s1.c_str(), s2.c_str()); // s1 地址變化,s2 不變
輸出證明:修改前共享,修改後分離。
不能放在 string 對象內(每個對象一份),不能放全局(所有 string 共享)。方案:緩衝區頭部/尾部額外分配空間存放 RefCnt。
緩衝區佈局:[RefCnt][字符數據\0]
↑
_Ptr (指向數據區)
RefCnt 位於 _Ptr[-1] 或 _Ptr[len+1]
所有共享對象的 _Ptr 指向同一數據區,自然共享同一 RefCnt。
operator[]operator=、+=、append、insert、replace 等只讀操作(c_str()、size()、const 索引)不觸發 COW。
// 構造:分配 len+1+1,最後 1 字節存 RefCnt=1
// 拷貝構造/賦值:共享 _Ptr,RefCnt++
// COW 修改:
if (RefCnt > 1) {
char* tmp = new char[len+1+1];
memcpy(tmp, _Ptr, len+1);
_Ptr = tmp;
RefCnt = 1; // 新緩衝區計數歸 1
原緩衝區 RefCnt--
}
return _Ptr[idx]; // 返回可修改引用
// myNet.dll
string GetIP(string host) {
static string ip; // 靜態緩衝區
return ip; // 拷貝構造 → 共享 ip 內部緩衝區
}
// 主程序
string ip = GetIP("host"); // ip 與 dll 內部 ip 共享緩衝區
FreeLibrary(hDll); // dll 卸載,靜態 ip 析構,緩衝區釋放
cout << ip; // 訪問已釋放內存 → Crash
修復:string ip = GetIP("host").c_str(); 強制複製內容。
string::operator[] 常數時間、不拋異常,COW 難滿足GCC 5+、Clang、MSVC 現代 STL 均已移除 COW,採用 SSO + 移動語義。
COW 是單線程時代的經典優化,但在多核、動態庫、標準演進下暴露出線程安全、ABI 邊界、性能不可預測等缺陷。理解 COW 有助於讀懂舊代碼、設計引用計數系統,但新代碼請直接信任現代 string 實現。