langShift

記憶體管理基礎

記憶體管理是 C++ 程式設計的關鍵環節,它提供了對記憶體如何分配和釋放的精細控制。這與 JavaScript 的自動垃圾回收機制有顯著不同。

堆疊記憶體 vs. 堆積記憶體

在 C++ 中,記憶體主要在兩個區域進行管理:堆疊和堆積。

堆疊記憶體

  • 自動分配: 用於局部變數、函式參數和返回位址。
  • 後進先出 (LIFO): 記憶體以嚴格的順序分配和釋放。
  • 快速存取: 分配和釋放速度非常快。
  • 大小有限: 通常比堆積記憶體小。
  • 生命週期: 堆疊上的變數在其超出作用域時會自動釋放。
正在加载...

堆積記憶體

  • 動態分配: 用於在編譯時大小未知或生命週期超出函式作用域的資料。
  • 手動管理: 程式設計師必須使用 newdelete(或 malloc/free)明確地分配和釋放記憶體。
  • 較慢存取: 分配和釋放速度比堆疊記憶體慢。
  • 較大大小: 通常比堆疊記憶體大得多。
  • 生命週期: 記憶體會一直存在,直到明確釋放或程式終止。
正在加载...

變數生命週期

  • 自動 (堆疊) 變數: 在進入其作用域時建立,並在其作用域退出時銷毀。
  • 動態 (堆積) 變數: 使用 new 建立,並持續存在直到明確地 delete
  • 靜態變數: 在程式啟動時分配一次,並在程式的整個執行期間持續存在。
  • 全域變數: 類似於靜態變數,在程式啟動時分配,並在程式的生命週期內持續存在。

記憶體佈局基礎

典型的 C++ 程式記憶體佈局包括:

  • 文字/程式碼區段: 儲存可執行指令。
  • 資料區段: 儲存全域和靜態變數(已初始化和未初始化)。
  • 堆積: 動態分配的記憶體。
  • 堆疊: 局部變數和函式呼叫資訊。

與 JavaScript 記憶體模型的比較

JavaScript 使用垃圾回收器 (GC) 自動管理記憶體。當一個物件不再可達(不再被程式的任何部分引用)時,GC 會自動回收記憶體。這使開發人員無需手動記憶體管理,減少了記憶體洩漏和懸空指標等常見錯誤。

C++ 則需要手動記憶體管理。開發人員負責在需要時分配記憶體,並在不再需要時釋放記憶體。未能釋放記憶體會導致記憶體洩漏,而嘗試存取已釋放的記憶體會導致懸空指標使用後釋放錯誤。

記憶體洩漏概念

記憶體洩漏發生在程式在堆積上分配記憶體,但在不再需要時未能釋放它。這會導致可用記憶體逐漸消耗,可能導致程式甚至整個系統變慢或崩潰。

C++ 中記憶體洩漏的常見原因:

  • 忘記 delete 使用 new 分配的記憶體。
  • 遺失動態分配記憶體的指標。
  • 在錯誤路徑或例外情況下錯誤地處理記憶體。

基本記憶體管理原則

  1. 需要時分配,完成時釋放: 每個 new 都必須有對應的 delete(或 new[]delete[])。
  2. 所有權: 明確定義程式碼的哪個部分負責管理一段記憶體。
  3. RAII (資源取得即初始化): 一種 C++ 程式設計慣用語,將資源(如記憶體)的生命週期與物件的生命週期綁定。當物件超出作用域時,其解構函式會自動釋放資源。智慧指標是 RAII 的主要範例。

偵錯記憶體問題

偵錯 C++ 中的記憶體問題可能具有挑戰性。常用的工具包括:

  • Valgrind (Linux/macOS): 一個強大的記憶體偵錯器,可以偵測記憶體洩漏、使用後釋放錯誤和其他記憶體相關問題。
  • AddressSanitizer (Clang/GCC): 一個整合到編譯器中的快速記憶體錯誤偵測器。
  • Visual Studio 診斷工具 (Windows): Visual Studio 中用於記憶體分析和洩漏偵測的內建工具。

練習題:

  1. 解釋 C++ 中堆疊記憶體和堆積記憶體之間的關鍵差異。何時會選擇在堆積而不是堆疊上分配記憶體?
  2. 什麼是記憶體洩漏,它在 C++ 中如何發生?JavaScript 的記憶體管理方法如何緩解這個問題?
  3. 描述 RAII 原則以及它如何幫助 C++ 記憶體管理。

專案構想:

  • 編寫一個 C++ 程式,模擬一個簡單的鏈結串列。實作新增節點、移除節點和列印串列的函式。密切注意記憶體分配和釋放,以避免記憶體洩漏。明確使用 newdelete