閱讀 SGI STL 記憶體分配器二級分配器源碼時,會看到這一行:
_Obj* __STL_VOLATILE* __my_free_list;
乍看令人困惑:_Obj* 後緊跟 __STL_VOLATILE*?這究竟是什麼類型?本文將層層拆解。
首先查找宏定義 (通常在 stl_config.h 或類似配置頭文件):
#define __STL_VOLATILE volatile
因此原聲明等價於:
_Obj* volatile* __my_free_list;
遵循「右左法則」 (Clockwise/Spiral Rule):
__my_free_list 是標識符。* → 指針。volatile → 指向 volatile 修飾的實體 的指針。* → 該實體本身是 指針。_Obj → 基礎類型是 _Obj。結論:__my_free_list 是一個 指向「volatile _Obj*」的指針,即 二級指針,且中間層帶有 volatile 資格。
在二級分配器中,__my_free_list 指向空閒鏈表陣列的某個桶頭指針。volatile 告訴編譯器:
早期 STL 以此作為簡易執行緒同步手段 (現代 C++ 應用 std::atomic 或鎖)。
__my_free_list (地址 A)
│
▼
+-----------+ volatile _Obj* (地址 B) +-----------+
| 指針 | ─────────────────────────────► | 指針 | ──► _Obj 節點 (地址 C)
| (二級) | | (一級) | (空閒塊頭)
+-----------+ +-----------+
A B
A:__my_free_list 變數地址 (棧上或靜態區)。B:空閒鏈表桶頭指針地址 (陣列元素),類型 volatile _Obj*。C:實際空閒記憶體塊鏈表頭節點地址。// 取桶頭指針 (讀取 volatile 指針)
_Obj* volatile* bucket = &__my_free_list[index];
_Obj* head = *bucket; // 讀取 volatile 物件 → 強制從記憶體讀取
// 修改桶頭指針 (寫入 volatile 指針)
*bucket = new_head; // 寫入 volatile 物件 → 強制寫入記憶體,不可延遲
文中另一個例子:
#define outp_w(addr, dat) *(volatile UINT32*)(addr) = dat
同理:將 addr 轉為 volatile UINT32*,解引用賦值。確保對硬體暫存器的寫入即時生效,不被編譯器優化掉。
現代多執行緒代碼應棄用 volatile 做同步,改用:
std::atomic<_Obj*>:無鎖原子操作,明確記憶體序。std::mutex + 普通指針:互斥鎖保護臨界區。std::atomic_ref:對現有物件施加原子操作。volatile 僅保証「不優化訪問」,不保證原子性、不保證記憶體順序、不建立 happens-before 關係,不適用於現代多核同步。
_Obj* __STL_VOLATILE* __my_free_list 是「指向 volatile 一級指針的二級指針」。它反映了早期 STL 為多執行緒環境下空閒鏈表頭指針訪問所做的妥協:用 volatile 抑制編譯器優化,變相實現可見性。讀懂這類複雜聲明,是深入理解系統級 C++ 代碼的必修課。