S SmartDocs
Série: C++ cpp 266 lignes · Mis à jour 2026-04-03

dynamic_memory.cpp

C++/Part4_現代CPP/Ch16_記憶體管理與智慧指標/dynamic_memory.cpp

// ============================================================
// Ch16 — 動態記憶體管理(Dynamic Memory Management)
// 編譯:g++ -std=c++17 -Wall -o dynamic_memory dynamic_memory.cpp
// ============================================================

#include <iostream>
#include <string>

// ── 輔助函式:分隔線 ──
void section(const std::string& title) {
    std::cout << "\n========================================\n";
    std::cout << "  " << title << "\n";
    std::cout << "========================================\n";
}

// ── 用來示範動態記憶體的類別 ──
class Resource {
public:
    Resource(int id) : id_(id) {
        std::cout << "  Resource(" << id_ << ") 建構\n";
    }
    ~Resource() {
        std::cout << "  Resource(" << id_ << ") 解構\n";
    }
    void use() const {
        std::cout << "  使用 Resource(" << id_ << ")\n";
    }
private:
    int id_;
};

int main() {
    std::cout << "===== Ch16:動態記憶體管理 =====\n";

    // ──────────────────────────────────────────
    // 1. Stack vs Heap 基礎
    // ──────────────────────────────────────────
    section("1. Stack vs Heap 基礎");
    {
        // Stack 配置 — 離開作用域自動釋放
        int stackVar = 42;
        std::cout << "Stack 變數:" << stackVar << "\n";
        std::cout << "Stack 變數位址:" << &stackVar << "\n";

        // Heap 配置 — 需要手動 delete
        int* heapVar = new int(100);
        std::cout << "Heap 變數:" << *heapVar << "\n";
        std::cout << "Heap 變數位址:" << heapVar << "\n";
        delete heapVar;  // 手動釋放
    }
    std::cout << "(Stack 變數已自動釋放)\n";

    // ──────────────────────────────────────────
    // 2. new / delete 單一物件
    // ──────────────────────────────────────────
    section("2. new / delete 單一物件");
    {
        // 配置基本型別
        int* pi = new int(42);
        double* pd = new double(3.14);
        std::cout << "int: " << *pi << ", double: " << *pd << "\n";
        delete pi;
        delete pd;

        // 配置自訂類別
        std::cout << "\n配置 Resource 物件:\n";
        Resource* r = new Resource(1);
        r->use();
        delete r;  // 觸發解構子
    }

    // ──────────────────────────────────────────
    // 3. new[] / delete[] 陣列
    // ──────────────────────────────────────────
    section("3. new[] / delete[] 陣列");
    {
        const int SIZE = 5;
        int* arr = new int[SIZE];

        // 初始化
        for (int i = 0; i < SIZE; i++) {
            arr[i] = (i + 1) * 10;
        }

        // 存取
        std::cout << "動態陣列內容:";
        for (int i = 0; i < SIZE; i++) {
            std::cout << arr[i] << " ";
        }
        std::cout << "\n";

        delete[] arr;  // 注意:必須用 delete[]

        // 物件陣列
        std::cout << "\n配置 Resource 陣列(3 個):\n";
        Resource* resources = new Resource[3]{{10}, {20}, {30}};
        for (int i = 0; i < 3; i++) {
            resources[i].use();
        }
        std::cout << "釋放 Resource 陣列:\n";
        delete[] resources;
    }

    // ──────────────────────────────────────────
    // 4. 記憶體洩漏示範(Memory Leak)
    // ──────────────────────────────────────────
    section("4. 記憶體洩漏示範");
    {
        std::cout << "【問題程式碼(概念示範,此處有手動修正)】\n";
        std::cout << R"(
  void leakyFunction() {
      int* data = new int[1000];
      // 做一些事情...
      // 忘記 delete[] data;  ← 記憶體洩漏!
  }  // data 指標被銷毀,但 heap 上的記憶體未釋放
)";

        // 實際示範(為了不真的洩漏,我們還是釋放它)
        int* data = new int[100];
        for (int i = 0; i < 100; i++) data[i] = i;
        std::cout << "已配置 100 個 int(正確釋放)\n";
        delete[] data;

        // 函式中提前 return 造成的洩漏
        std::cout << "\n【提前 return 造成洩漏的模式】\n";
        std::cout << R"(
  bool process() {
      int* buffer = new int[1024];
      if (someCondition) {
          return false;     // ← buffer 洩漏!
      }
      delete[] buffer;
      return true;
  }
)";
    }

    // ──────────────────────────────────────────
    // 5. 懸空指標示範(Dangling Pointer)
    // ──────────────────────────────────────────
    section("5. 懸空指標示範");
    {
        std::cout << "【概念示範】\n";
        int* p = new int(42);
        std::cout << "刪除前:*p = " << *p << "\n";
        delete p;
        // 此時 p 仍然存著舊位址,但記憶體已釋放
        // std::cout << *p;  // 未定義行為!不要這樣做

        std::cout << "delete 後 p 仍然持有位址:" << p << "\n";
        std::cout << "但存取 *p 是未定義行為!\n";

        // 正確做法:delete 後設為 nullptr
        p = nullptr;
        std::cout << "設為 nullptr 後 p = " << p << "\n\n";

        // 另一種懸空指標:指向已離開作用域的區域變數
        std::cout << "【另一種懸空情況:回傳區域變數的位址】\n";
        std::cout << R"(
  int* dangerous() {
      int local = 42;
      return &local;  // ← 回傳區域變數位址 → 懸空指標!
  }
)";
    }

    // ──────────────────────────────────────────
    // 6. 重複刪除示範(Double Delete)
    // ──────────────────────────────────────────
    section("6. 重複刪除示範");
    {
        std::cout << "【概念示範 — 不實際執行以避免崩潰】\n";
        std::cout << R"(
  int* p = new int(42);
  int* q = p;          // q 和 p 指向同一塊記憶體
  delete p;            // 第一次刪除 — 正確
  delete q;            // 第二次刪除同一塊記憶體 — 未定義行為!
)";
        std::cout << "\n防範方式:\n";
        std::cout << "  1. delete 後立即設為 nullptr(delete nullptr 是安全的)\n";
        std::cout << "  2. 明確誰「擁有」指標(誰負責 delete)\n";
        std::cout << "  3. 使用智慧指標(最佳解法)\n";

        // 安全的做法
        int* safe = new int(42);
        delete safe;
        safe = nullptr;
        delete safe;  // delete nullptr 是安全的,不會有任何副作用
        std::cout << "\ndelete nullptr 是安全的 ✓\n";
    }

    // ──────────────────────────────────────────
    // 7. 例外安全問題
    // ──────────────────────────────────────────
    section("7. 例外造成的洩漏問題");
    {
        std::cout << "【問題程式碼】\n";
        std::cout << R"(
  void unsafeFunction() {
      Resource* r1 = new Resource(1);
      Resource* r2 = new Resource(2);  // 如果這裡拋出例外...
      // ...做一些事情,可能拋出例外...
      delete r2;                        // 這些永遠不會執行
      delete r1;
  }
)";
        std::cout << "\n解法:使用智慧指標\n";
        std::cout << R"(
  void safeFunction() {
      auto r1 = std::make_unique<Resource>(1);
      auto r2 = std::make_unique<Resource>(2);
      // 即使拋出例外,資源也會自動釋放
  }
)";
    }

    // ──────────────────────────────────────────
    // 8. new / delete 配對規則總結
    // ──────────────────────────────────────────
    section("8. new / delete 配對規則總結");
    {
        std::cout << "┌──────────────────┬──────────────────┐\n";
        std::cout << "│   配置方式        │   釋放方式        │\n";
        std::cout << "├──────────────────┼──────────────────┤\n";
        std::cout << "│ new T            │ delete p         │\n";
        std::cout << "│ new T[n]         │ delete[] p       │\n";
        std::cout << "│ new T(args)      │ delete p         │\n";
        std::cout << "│ new T[n]{...}    │ delete[] p       │\n";
        std::cout << "└──────────────────┴──────────────────┘\n";
        std::cout << "\n混用 delete 和 delete[] 會導致未定義行為!\n";
    }

    // ──────────────────────────────────────────
    // 9. 為什麼要用智慧指標
    // ──────────────────────────────────────────
    section("9. 為什麼要用智慧指標");
    {
        std::cout << "原始 new/delete 的問題:\n";
        std::cout << "  ✗ 容易忘記 delete → 記憶體洩漏\n";
        std::cout << "  ✗ 不確定誰「擁有」指標 → 重複刪除或遺漏\n";
        std::cout << "  ✗ 例外拋出時跳過 delete → 洩漏\n";
        std::cout << "  ✗ 提前 return 跳過 delete → 洩漏\n";
        std::cout << "\n智慧指標的優點:\n";
        std::cout << "  ✓ 自動管理記憶體(RAII)\n";
        std::cout << "  ✓ 擁有權語意清晰\n";
        std::cout << "  ✓ 例外安全\n";
        std::cout << "  ✓ unique_ptr 零額外開銷\n";

        // 簡單對比
        std::cout << "\n--- 傳統寫法 ---\n";
        {
            Resource* r = new Resource(100);
            r->use();
            delete r;
        }
        std::cout << "\n--- 現代寫法(下一個範例檔會詳細示範) ---\n";
        std::cout << R"(
  auto r = std::make_unique<Resource>(100);
  r->use();
  // 離開作用域自動釋放,不需要 delete
)";
    }

    std::cout << "\n===== 動態記憶體管理示範結束 =====\n";
    return 0;
}

Articles liés