文章目錄
- 1. 虛擬內存空間
- 2. malloc和free
- 3. new和delete
- 4. 內存池
1. 虛擬內存空間
程序進程的虛擬內存空間是操作系統為每個進程提供的獨立、連續的邏輯地址空間,與物理內存解耦。其核心目的是隔離進程、簡化內存管理,并提供靈活的內存訪問控制。
(圖片取自于:【C++】內存管理分布_c++內存分布-CSDN博客)
自下而上為低地址到高地址。
以Linux x86-64架構為例:
虛擬內存空間分為用戶空間和內核空間
- 用戶空間(低地址 → 高地址):進程可直接訪問的區域,通常占據虛擬地址空間的大部分。
- 內核空間(高地址保留區):操作系統內核專用,進程無法直接訪問,需通過系統調用交互。
用戶空間分為:
-
代碼段(Text Segment)
- 地址范圍:
0x400000
附近(起始地址)。 - 內容:編譯后的可執行機器指令(如程序的
main
函數)。 - 權限:只讀(Read-Only)、可執行(Execute),不可修改。
- 特點:多個進程可共享同一代碼段(如動態庫)。
- 地址范圍:
-
初始化數據段(Data Segment)
- 地址范圍:緊鄰代碼段。
- 內容:全局變量、靜態變量(已顯式初始化,如
int a = 10;
)。 - 權限:可讀寫(Read-Write),不可執行。
-
未初始化數據段(BSS Segment)
- 地址范圍:緊鄰初始化數據段。
- 內容:未顯式初始化的全局/靜態變量(如
int b;
),默認值為0。 - 權限:可讀寫,不可執行。
-
堆(Heap)
- 地址范圍:向高地址動態增長。
- 內容:動態分配的內存(如
malloc()
、new
)。 - 權限:可讀寫,不可執行。
- 管理:由程序員控制分配/釋放,可能產生內存碎片。
-
內存映射區域(Memory Mapping Segment)
-
地址范圍:堆與棧之間的動態區域。
-
內容:
- 動態鏈接庫(如
libc.so
)。 - 文件映射(
mmap
系統調用,如加載共享內存或大文件)。 - 匿名映射(用于大塊內存分配,如某些
malloc
實現)。
- 動態鏈接庫(如
-
權限:可自定義(讀/寫/執行)。
-
-
棧(Stack)
-
地址范圍:用戶空間頂端附近,向低地址增長。
-
內容:
- 函數調用棧幀(局部變量、函數參數、返回地址)。
- 線程棧(每個線程有獨立棧空間)。
-
權限:可讀寫,不可執行。
-
管理:自動分配/釋放,由編譯器控制,大小有限(可能棧溢出)。
-
-
環境變量與命令行參數
- 地址范圍:棧的頂部區域。
- 內容:
argv
(命令行參數)、envp
(環境變量)的字符串數組。 - 權限:可讀寫。
內核空間
- 地址范圍:64位系統中通常為高地址的
0xffff800000000000
以上。 - 內容:
- 內核代碼、數據結構。
- 進程頁表、硬件驅動、中斷處理程序等。
- 權限:僅內核態可訪問,用戶態訪問會觸發段錯誤。
2. malloc和free
參考文章:malloc 底層實現及原理_malloc底層原理-CSDN博客
malloc:
- 當分配的大小小于128KB時,通過
brk()
系統調用從堆段分配內存 - 當分配的大小大于等于128KB時,通過
mmap()
系統調用從文件映射段分配內存
注意:這兩種方式分配的都是虛擬內存,沒有分配物理內存。在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然后建立虛擬內存和物理內存之間的映射關系。
free:
在實際分配內存時會多分配16字節(位于元數據之前)用來記錄內存塊描述信息,其中包括內存塊的大小。
當調用free
釋放內存時,傳入的指針會首先向前偏移16字節,獲取內存塊的信息,然后再釋放。
- 對于
brk()
申請的小空間,會標記為空間,并不會直接釋放。當連續空閑空間大于128KB時會執行內存緊縮操作(trim) - 對于
mmap()
申請的大空間,會調用munmap()
歸還給操作系統
3. new和delete
參考文章:【C++】內存管理分布_c++內存分布-CSDN博客
new
的底層步驟
- 內存分配:調用
operator new
函數(內部通常基于malloc
)申請指定大小的內存。 - 對象構造:在分配的內存上調用對象的構造函數(初始化成員變量等)。
- 異常處理:若內存不足,
operator new
拋出std::bad_alloc
異常(除非使用nothrow
版本)。
底層展開:
void* ptr = operator new(sizeof(MyClass)); // 內部調用 malloc
MyClass::MyClass(ptr); // 構造函數
delete
的底層步驟
- 對象析構:調用對象的析構函數(清理資源,如釋放句柄)。
- 內存釋放:調用
operator delete
函數(內部通常基于free
)釋放內存。
底層展開:
MyClass::~MyClass(obj); // 析構函數
operator delete(obj); // 內部調用 free
與malloc
和free
的對比
特性 | new /delete | malloc /free |
---|---|---|
語言層面 | C++ 運算符,支持重載 | C 標準庫函數,不可重載 |
內存初始化 | 調用構造函數/析構函數 | 僅分配/釋放原始內存,無初始化邏輯 |
類型安全 | 返回類型明確指針(如 MyClass* ) | 返回 void* ,需手動類型轉換 |
內存大小計算 | 自動計算類型所需大小(如 new int ) | 需手動指定字節數(如 malloc(4) ) |
錯誤處理 | 內存不足時拋出異常(可捕獲) | 返回 NULL ,需檢查返回值 |
數組支持 | 支持 new[] 和 delete[] | 需手動計算數組大小,無內置支持 |
底層擴展性 | 可自定義 operator new /operator delete | 無法修改 malloc /free 行為 |
適用場景 | C++ 對象管理(含構造/析構) | 原始內存操作或與 C 代碼交互 |
異常與錯誤處理
-
new
:失敗時拋出std::bad_alloc
,可通過try-catch
捕獲。try {int* arr = new int[1000000000000]; } catch (const std::bad_alloc& e) {std::cerr << "內存不足: " << e.what() << std::endl; }
-
malloc
:失敗時返回NULL
,需顯式檢查。int* arr = (int*)malloc(1000000000000 * sizeof(int)); if (arr == nullptr) {perror("malloc 失敗"); }
內存對齊與重載
-
new
:支持自定義內存對齊(C++17 的align_val_t
)和重載。// 自定義 operator new void* operator new(size_t size, const char* tag) {std::cout << "通過標簽分配: " << tag << std::endl;return malloc(size); } MyClass* obj = new ("DEBUG") MyClass();
-
malloc
:對齊由實現決定,無法直接定制。
數組處理
-
new[]
:自動計算數組元素總大小,并為每個元素調用構造函數。MyClass* arr = new MyClass[5]; // 調用 5 次構造函數 delete[] arr; // 調用 5 次析構函數
-
malloc
:需手動計算總字節數,且不構造對象。MyClass* arr = (MyClass*)malloc(5 * sizeof(MyClass)); free(arr); // 不會調用析構函數,可能導致資源泄漏
4. 內存池
內存池(Memory Pool)是一種預先分配并統一管理內存資源的技術,旨在優化動態內存分配的效率和性能。
4.1 基本概念
- 預先分配:在程序初始化階段,一次性向操作系統申請一大塊連續內存(稱為“池”)。
- 自主管理:程序自行管理池內的內存分配與回收,避免頻繁調用系統級函數(如
malloc
/free
)。 - 按需分配:從池中劃分小塊內存供程序使用,釋放時標記為可復用而非立即歸還操作系統。
4.2 核心優勢
特性 | 傳統malloc /free | 內存池 |
---|---|---|
分配速度 | 需遍歷復雜數據結構(如Bins) | 直接定位空閑塊,速度極快 |
內存碎片 | 易產生外部/內部碎片 | 碎片可控,甚至完全消除(固定塊) |
系統調用開銷 | 頻繁調用brk /mmap | 僅初始化和銷毀時調用 |
線程安全 | 全局鎖可能引發競爭 | 可設計為線程私有池或無鎖結構 |
適用場景 | 通用、動態需求 | 高頻次、固定/可預測內存需求 |
4.3 實現方式
固定大小內存池
-
機制:池中所有內存塊大小相同(如4KB)。
-
管理:使用空閑鏈表(Free List)跟蹤可用塊。
struct MemoryBlock {MemoryBlock* next; // 指向下一個空閑塊 }; MemoryBlock* free_list; // 空閑鏈表頭
-
操作:
- 分配:從鏈表頭部取一個塊,時間復雜度O(1)。
- 釋放:將塊插回鏈表頭部,時間復雜度O(1)。
-
優點:零碎片、極快分配。
-
缺點:無法處理變長需求,可能浪費內存。
可變大小內存池
- 機制:支持不同大小的內存請求。
- 管理:
- 分離空閑鏈表:為不同大小范圍維護多個鏈表(如8B、16B、32B…)。
- 伙伴系統(Buddy System):按2的冪次分割內存塊,合并相鄰空閑塊。
- 分配時向上取整到最近的2^n大小。
- 釋放時檢查“伙伴塊”是否空閑,若空閑則合并。
- 優點:靈活支持變長請求,減少碎片。
- 缺點:管理復雜度高,存在內部碎片。
4.4 典型應用場景
-
高頻次小對象分配
- 如網絡服務器為每個請求分配臨時緩沖區。
- 示例:Nginx使用內存池管理HTTP請求資源。
-
實時系統
確保內存分配時間確定性,避免傳統
malloc
的不可預測延遲。 -
游戲開發
快速創建/銷毀大量游戲實體(如子彈、粒子效果),通過對象池(Object Pool)實現。
-
嵌入式系統
資源受限環境,需嚴格控制內存使用和碎片。
4.5 示例
#define BLOCK_SIZE 64 // 固定塊大小
#define POOL_SIZE 100 // 池中塊數量typedef struct MemoryBlock {struct MemoryBlock* next;
} MemoryBlock;MemoryBlock* free_list = NULL;// 初始化內存池
void init_pool() {static char pool[BLOCK_SIZE * POOL_SIZE];for (int i = 0; i < POOL_SIZE; i++) {MemoryBlock* block = (MemoryBlock*)(pool + i * BLOCK_SIZE);block->next = free_list;free_list = block;}
}// 分配內存
void* pool_alloc() {if (!free_list) return NULL; // 池耗盡MemoryBlock* block = free_list;free_list = free_list->next;return (void*)block;
}// 釋放內存
void pool_free(void* ptr) {MemoryBlock* block = (MemoryBlock*)ptr;block->next = free_list;free_list = block;
}
(注:以上內容參考自DeepSeek)