內存分配的每一層面
applications可以調用STL,里面會有allocator進行內存分配;也可以使用C++ 基本工具primitives,比如new, new[], new(), ::operator new();還可以使用更底層的malloc和free分配和釋放內存。最底層的是系統調用,比如HeapAlloc,VirtualAlloc
在C中,malloc 和 free 是標準庫函數,不涉及構造函數和析構函數,只是簡單的內存分配和釋放
int *ptr = (int *)malloc(sizeof(int));
free(ptr);
在C++中,new 和 delete 不僅僅是內存分配和釋放的操作符,還會處理對象的構造和析構
四個層面的基本用法
malloc和 ::operator new()是完全一樣的效果
allocator()是創建一個臨時對象來調用非static函數
__GNUC__版本4.9的分配器
基本構件之一new delete expression上
由于有分配內存,因此要try catch考慮內存分配失敗時如何處理
new的過程:1.分配內存 2.指針轉型 3.調用構造函數
在指定內存上創建對象使用placement new:new (pointer) Type(initializer);
void* memory = operator new(sizeof(MyClass)); // 分配內存
MyClass* obj = new (memory) MyClass(/* constructor arguments */); // 在指定內存位置創建對象
void* memory = operator new[](sizeof(MyClass) * 5); // 分配數組內存
MyClass* objArray = new (memory) MyClass[5]; // 在數組內存中創建對象
_callnewh 不是 C++ 標準中的函數,而是可能是用戶定義的一個函數。通常情況下,這類函數的名字以 _new_handler 結尾,用于處理內存分配失敗的情況。
在 C++ 中,當 new 表達式無法分配所需的內存時,會調用用戶指定的 new_handler 函數。new_handler 是一個函數指針,指向一個用戶定義的函數,其原型通常為
typedef void (*new_handler)();
這個函數可以嘗試釋放內存、擴大內存池,或者執行其他操作來嘗試解決內存不足的問題。如果 new_handler 能夠成功處理內存不足的情況,返回;如果不能處理,可以選擇拋出異常或者終止程序
基本構件之一new delete expression中
使用定位 new 運算符后,必須手動調用對象的析構函數來釋放資源,否則可能導致內存泄漏
obj->~MyClass(); // 手動調用析構函數
operator delete(memory); // 手動釋放內存
delete的動作:先調用析構函數,然后釋放內存。
operator delete里調用free
基本構件之一new delete expression下
ctor和dtor直接調用的測試
Array new
cookie記錄的是下面一塊的長度。malloc分配的時候會額外帶上一塊cookie的信息,供給free釋放
測試
vc6下malloc new int[10]內存布局:灰色表示具體數據,橙色是debug模式下添加的內存。上面和最下面的兩個0x61(61H)是cookie,記錄整體內存分配的大小。61H實際上是60H,表示內存分配的大小,后面1H意思是占用最后一位,表示內存分配出去。淺藍色的pad表示補齊,填補到16的倍數
placement new
placement new允許我們將對象建構在已經分配好的內存中
Complex* pc = new(buf)Complex(1, 2);這句話會被編譯器轉換為,分別調用operator new(需要第二個參數,表示位置,這個函數只是傳回這個位置,不再分配內存),指針轉型,調用構造函數
重載
new是表達式,不可改變不可重載。會調用 operator new,全局(可重載但少見)或者成員函數(可重載)
容器里把構造函數和析構函數包裝在 construct()和destroy(),內存分配動作allocate()和deallocate()會走入分配器allocator中處理
容器分配內存的一般途徑:容器使用分配器,在這里插入圖片描述
分配器調用 ::operator new 和 ::operator delete,底層可能調用 malloc 和 free:
重載全局的::operator new 和::operator delete
在一個類中重載operator new和operator delete。編譯器會自動調用
通常會加static,因為調用的地方通常在創建對象的過程中,無法通過對象來調用一般函數
重載示例
重載示例
有虛函數只是把大小放大了,一個12,一個16
在GNU C++4.9版本中構造是從上到下,析構是從下到上
使用全局new,delete示例
placement new的重載第一參數必須是size_t類型,接受類的大小,會傳入Foo的大小。其余的參數就是括號里的參數
平常使用的string,就是個typedef ,define basic_string。重載了operator new。每次創建時也額外分配字符串大小的內存
Per class allocator
內存池:用malloc分配一大塊(內存池),然后分成小塊,減少malloc的調用次數。另外減少cookie的用量。
在Screen類中引入一個指針next,它的大小是4B,用于串聯鏈表
delete操作,把指針p回收到單向鏈表中,放到鏈表的頭指針位置
左邊間隔8表示每個Screen對象內存分配的大小為8B,說明每個Screen分配的時候沒有cookie。
右邊間隔16,表示每個Screen對象內存分配的大小為16B,這是因為對象分配的時候上下加了cookie,最上面和最下面的cookie大小共為8B
Per class allocator 2
struct AirplaneRep,由于對齊,5B會變成8B
union 是一種特殊的數據結構,可以看作同一個東西,用不同的名稱/不同的角度去看待。
上述例子用指針去看待union時,只看8個字節的前4個字節
通過union借用內存塊的前4個字節當作指針
delete沒有free,只是把區塊回收到鏈表,并未把內存還給操作系統,鏈表可能會越來越長,超過512塊
Static allocator
從軟件工程的角度看,上面的operator new和operator delete對于不同 類都要重載,明顯不是一個好的解法,因此將allocator抽象成一個類。
具體的類進行內存分配的時候,只需要調用allocator即可
嵌入式指針embedded pointer:借用A對象所占用的內存空間中的前4個字節,這4個字節用來 鏈住這些空閑的內存塊;但是,一旦某一塊被分配出去,那么這個塊的 前4個字節 就不再需要。因此類A對象的sizeof必須不小于4字節
上述例子定義一個類型obj,不放在外部,污染全局變量。struct里放了一個指針,它的大小為4個字節。這個指針的值,存著下一個內存的地址。由于這里只需要指針,所以union可以不使用。
由于上面的CHUNK設置為5,每5個對象的內存空間是連續的(間隔都是一個對象的大小),而每個大塊之間是不連續的。
Macro for static allocator
把allocator的部分拿出來用宏來定義
宏是預處理指令,用于在編譯過程中執行文本替換。宏通常通過 #define 關鍵字定義,并在代碼中通過宏名稱來調用。它們是一種簡單的文本替換機制,可以用于創建常量、函數替代、條件編譯等。
在宏定義的末尾使用反斜杠是為了告訴編譯器該宏定義將在下一行繼續。如果在宏定義的最后一行沒有使用反斜杠,那么編譯器會認為宏定義結束了
版本1:指針,版本2:embedded pointer,版本3:抽取內存的動作到單一class Allocator 版本4:alloctator通過宏抽取出來
標準庫中的allocator
其中一種分配器有16條自由鏈表,來應對不同大小的塊分配,不同的大小的類對象,分配到不同的鏈表中
New Handler
new handler 是一個函數指針,當 new 操作符無法分配所需的內存時,會調用與之關聯的 new handler
new handler 是全局的,一旦設置,會在程序的生命周期內一直有效,直到被其他 set_new_handler 覆蓋
new handler的例子