1. C++記憶體區域
C++程式的記憶體通常分為以下幾部分:
① 程式碼區(Code Segment)
- 儲存程式的機器程式碼,即編譯後的可執行程式碼。程式碼區通常是隻讀的,以防止程式碼在執行時被意外修改,確保安全性。
- 程式碼區在程式載入時由作業系統分配。
② 全域性/靜態區(Data Segment)
- 全域性變數和靜態變數:儲存生命週期為整個程式執行時的變數,分為已初始化區和未初始化區(BSS段)。
- 已初始化資料段:存放已初始化的全域性變數和靜態變數。
- 未初始化資料段(BSS段):存放未初始化的全域性變數和靜態變數,系統會將其初始化為零。
- 常量區:儲存字串字面量和
const
修飾的全域性或靜態變數等,這些內容在程式執行期間不可修改。
③ 堆區(Heap Segment)
- 堆區用於動態記憶體分配(如使用
new
或malloc
),由程式設計師手動管理(分配和釋放)。 - 堆的增長方向通常是從低地址向高地址增長。由於堆區不受程式自動管理,如果分配的記憶體未被釋放,可能會導致記憶體洩漏。
- C++的
new
運算子會從堆中分配記憶體,並在其不再使用時呼叫delete
釋放。
④ 棧區(Stack Segment)
- 棧區用於儲存區域性變數、函式引數和返回地址等資料,每當函式被呼叫時分配記憶體,函式結束時自動釋放。
- 棧的增長方向通常是從高地址向低地址增長。
- 棧記憶體由作業系統管理,具有分配速度快的特點,但容量有限,棧溢位可能導致程式崩潰。
- 棧的佈局一般包括棧幀、返回地址和引數資訊等,用於維護函式呼叫的上下文。
- 棧的分配速度快,堆則稍慢,但堆的容量通常大於棧。
記憶體佈局示意圖:
2. 記憶體管理方式
2.1 棧上分配
棧上的記憶體分配和釋放是自動完成的,不需要程式設計師手動管理。棧記憶體用於儲存區域性變數、函式引數等臨時資料。
2.2 堆上分配
堆上記憶體分配是動態的,需要程式設計師手動分配和釋放。new
運算子用於在堆上分配記憶體,delete
用於釋放記憶體。
- 動態陣列分配:
注意:
如果沒有及時使用delete
釋放記憶體,就會導致記憶體洩漏(memory leak)。這是因為在程式執行期間,未釋放的記憶體將無法被再次使用。
3. 智慧指標(Smart Pointers)
在現代C++中,智慧指標用於簡化記憶體管理,減少記憶體洩漏的風險。C++11提供了幾種常見的智慧指標:
-
std::unique_ptr
:
獨佔式所有權,表示該物件只有一個所有者,離開作用域時會自動釋放記憶體。 -
std::shared_ptr
:
共享所有權,可以有多個指標指向同一個物件。當最後一個指向物件的shared_ptr
被銷燬時,物件的記憶體才會被釋放。 -
std::weak_ptr
:
輔助shared_ptr
使用,不會增加物件的引用計數,常用於解決shared_ptr
迴圈引用的問題。
4. 常見的記憶體管理問題
4.1 記憶體洩漏(Memory Leak)
記憶體洩漏是指程式在堆上分配了記憶體,但沒有釋放,導致記憶體永久佔用。長時間執行的程式(如伺服器)如果出現記憶體洩漏,可能會耗盡系統記憶體。
示例:
4.2 懸空指標(Dangling Pointer)
懸空指標指的是指向已經被釋放記憶體的指標,若嘗試訪問該記憶體會導致未定義行為。
示例:
4.3 野指標(Wild Pointer)
野指標是指未初始化的指標,其指向未知的記憶體地址,容易導致程式崩潰。
示例:
5. C++ RAII原則
RAII(Resource Acquisition Is Initialization,資源獲取即初始化)是一種重要的記憶體管理原則。
它強調在構造物件時獲取資源,並在物件銷燬時釋放資源。智慧指標和標準庫容器(如std::vector
)都遵循RAII原則,可以自動管理記憶體的分配和釋放,減少了手動管理的麻煩。遵循RAII原則可以有效減少記憶體管理的錯誤,提高程式的安全性和健壯性。
示例: