【C/C++】4.C++的記憶體管理

朝槿yys發表於2024-10-29

1. C++記憶體區域

C++程式的記憶體通常分為以下幾部分:

程式碼區(Code Segment)

  • 儲存程式的機器程式碼,即編譯後的可執行程式碼。程式碼區通常是隻讀的,以防止程式碼在執行時被意外修改,確保安全性。
  • 程式碼區在程式載入時由作業系統分配。

② 全域性/靜態區(Data Segment)

  • 全域性變數和靜態變數:儲存生命週期為整個程式執行時的變數,分為已初始化區和未初始化區(BSS段)。
    • 已初始化資料段:存放已初始化的全域性變數和靜態變數。
    • 未初始化資料段(BSS段):存放未初始化的全域性變數和靜態變數,系統會將其初始化為零。
  • 常量區:儲存字串字面量和const修飾的全域性或靜態變數等,這些內容在程式執行期間不可修改。

③ 堆區(Heap Segment)

  • 堆區用於動態記憶體分配(如使用newmalloc),由程式設計師手動管理(分配和釋放)。
  • 堆的增長方向通常是從低地址向高地址增長。由於堆區不受程式自動管理,如果分配的記憶體未被釋放,可能會導致記憶體洩漏
  • C++的new運算子會從堆中分配記憶體,並在其不再使用時呼叫delete釋放。

棧區(Stack Segment)

  • 棧區用於儲存區域性變數、函式引數和返回地址等資料,每當函式被呼叫時分配記憶體,函式結束時自動釋放。
  • 棧的增長方向通常是從高地址向低地址增長。
  • 棧記憶體由作業系統管理,具有分配速度快的特點,但容量有限,棧溢位可能導致程式崩潰。
  • 棧的佈局一般包括棧幀、返回地址和引數資訊等,用於維護函式呼叫的上下文。
  • 棧的分配速度快,堆則稍慢,但堆的容量通常大於棧。

記憶體佈局示意圖:


2. 記憶體管理方式

2.1 棧上分配

棧上的記憶體分配和釋放是自動完成的,不需要程式設計師手動管理。棧記憶體用於儲存區域性變數、函式引數等臨時資料。

void exampleFunction() {
int x = 10; // x 是棧上的區域性變數
int y = 20; // y 是棧上的區域性變數
} // exampleFunction結束後,x 和 y 的記憶體會自動釋放

2.2 堆上分配

堆上記憶體分配是動態的,需要程式設計師手動分配和釋放。new運算子用於在堆上分配記憶體,delete用於釋放記憶體。

void exampleFunction() {
int* ptr = new int(10); // 動態分配一個int型別的記憶體,初始值為10
delete ptr; // 釋放記憶體
}
  • 動態陣列分配
    int* array = new int[5]; // 分配一個包含5個整數的陣列
    delete[] array; // 使用 delete[] 釋放陣列

注意:

如果沒有及時使用delete釋放記憶體,就會導致記憶體洩漏(memory leak)。這是因為在程式執行期間,未釋放的記憶體將無法被再次使用。


3. 智慧指標(Smart Pointers)

在現代C++中,智慧指標用於簡化記憶體管理,減少記憶體洩漏的風險。C++11提供了幾種常見的智慧指標:

  • std::unique_ptr
    獨佔式所有權,表示該物件只有一個所有者,離開作用域時會自動釋放記憶體。

    #include <memory>
    std::unique_ptr<int> ptr = std::make_unique<int>(10); // 自動釋放記憶體
  • std::shared_ptr
    共享所有權,可以有多個指標指向同一個物件。當最後一個指向物件的shared_ptr被銷燬時,物件的記憶體才會被釋放。

    #include <memory>
    std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
    std::shared_ptr<int> ptr2 = ptr1; // ptr1 和 ptr2共享同一個物件
  • std::weak_ptr
    輔助shared_ptr使用,不會增加物件的引用計數,常用於解決shared_ptr迴圈引用的問題。

    #include <memory>
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);
    std::weak_ptr<int> weakPtr = sharedPtr; // weakPtr不會影響sharedPtr的引用計數

4. 常見的記憶體管理問題

4.1 記憶體洩漏(Memory Leak)

記憶體洩漏是指程式在堆上分配了記憶體,但沒有釋放,導致記憶體永久佔用。長時間執行的程式(如伺服器)如果出現記憶體洩漏,可能會耗盡系統記憶體。

示例:

void memoryLeakExample() {
int* ptr = new int(10); // 忘記釋放 ptr,導致記憶體洩漏
}

4.2 懸空指標(Dangling Pointer)

懸空指標指的是指向已經被釋放記憶體的指標,若嘗試訪問該記憶體會導致未定義行為。

示例:

void danglingPointerExample() {
int* ptr = new int(10);
delete ptr; // 釋放記憶體
// ptr 現在是懸空指標
}

4.3 野指標(Wild Pointer)

野指標是指未初始化的指標,其指向未知的記憶體地址,容易導致程式崩潰。

示例:

void wildPointerExample() {
int* ptr; // 未初始化,ptr是野指標
*ptr = 10; // 可能導致程式崩潰
}

5. C++ RAII原則

RAII(Resource Acquisition Is Initialization,資源獲取即初始化)是一種重要的記憶體管理原則。
它強調在構造物件時獲取資源,並在物件銷燬時釋放資源。智慧指標和標準庫容器(如std::vector)都遵循RAII原則,可以自動管理記憶體的分配和釋放,減少了手動管理的麻煩。遵循RAII原則可以有效減少記憶體管理的錯誤,提高程式的安全性和健壯性。

示例:

#include <iostream>
#include <vector>
void raiiExample() {
std::vector<int> vec = {1, 2, 3, 4, 5}; // 使用 vector 自動管理記憶體
// vector 離開作用域時自動釋放記憶體
}

相關文章