一個簡易的C++內存管理器實現總體結構如下所示:
該內存分配器的剛開始使用是通過sc_pool_s *sc_create_pool (size_t size)
函數創建一個字節大小為size的sc_pool_s
對象,該對象由一個鏈表結構的sc_pool_data_t
和一個指向sc_pool_large_s
的結構體指針組成,其中sc_pool_data_t
用來處理小內存申請的需求,而sc_pool_large_s
則用來處理大內存申請的請求,且根據需要動態的擴展,形成一個鏈表結構。
一些其他內容的說明
1. 內存對齊方式的實現
#define sc_align_ptr(p, a) \
(unsigned char *) (((unsigned long int ) (p) + ((unsigned long int ) a - 1)) & ~((unsigned long int ) a - 1))
這段代碼的意思就是返回一個以大小為a
的內存對齊方式,要解釋這段代碼,假設里面的變量都是unsigned long
類型的,將其簡化成如下形式:
(p +(a - 1)) & ~(a - 1)
先解釋~(a - 1)
,假設現在的對齊方式是16字節的方式對齊(一般都是將地址2的冪次進行對齊),即a = 16
,現在將a - 1
取反后的二進制為10000
,將這個二進制數與任何數相與,都會產生后4位為0的二進制數,而其他位數不變,這個數此時恰好就是a = 16
的倍數。而p + (a - 1)
則是要獲取比起始地址p
要大的數,且這個數要盡可能的不會影響原來已經分配了內存的空間,但又不會比p
大太多從而造成內存碎片,則所加的數是a-1
滿足該要求。
例如此時p的基址為17(假設從0開始分配,已經為某處分配了17字節的大小的內存,現在從p=17開始申請內存)。p + (a - 1)
的值為0...010001 + 0...01111 = 100000
,此時(p +(a - 1)) & ~(a - 1)
相與的結果是0...0100000
& 1...10000
=0...0100000
= 32;即地址不會從17開始,而只會從32開始,有利於快速讀取操作。
內存對齊方式的原因
- 內存對齊后,可以更方便讀取的存取數據。
- 不是全部硬件平台都能訪問隨意地址上的隨意地址,為了兼容性,需要做內存對齊。
解釋
對於大多數語言來說,這部分實現是由編譯器來實現的(編譯器將每個”數據單元“放在合適的內存位置上),但是C/C++太強大,太靈活了,允許你去干預”內存對齊“。
以如下程序做說明:
struct{
char a;
char b;
int c;
} Struct1;
struct{
char a;
int c;
char b;
} Struct2;
int main()
{
cout << "Struct1 : " << sizeof(Struct1) << endl; // Struct1 : 8
cout << "Struct2 : " << sizeof(Struct2) << endl; // Struct2 : 12
return 0;
}
原因是linux中默認對齊方式的值為4,在struct1
中,前兩個字節表示共占2字節的大小,第一塊內存的位置可以放下,然后一個int
占4個字節,而第一個內存里只能容下兩個字節的數據存放,所以就會放到第二塊內存位置中去;同理,在結構體struct2
中,第一塊內存只能容納一個char
類型的字符,而4字節的int
要放到 第二塊內存里,第三個char
此時只能放到 第三塊內存中去了。
結構體 | 內存塊1(4字節) | 內存塊2(4字節) | 內存塊3(4字節) |
---|---|---|---|
Struct1 |
a,b | c | |
Struct2 |
a | c | b |
經過內存對齊后,CPU的內存訪問速度大大提升。如果操作1字節的數據,可以是任意地址;若是操作2字節的數據,如果開始地址在偶數地址,一次就可以取2字節,如果開始地址在奇數,就要2次內存操作才能完成;如果操作4字節的數據,最好開始地址在能被4整除的數值上,這樣可以用一條32位的內存操作指令完成。同樣,8字節的開始位置最好的能被8整除的數值上,這樣可以用一條64位的內存操作指令完成。就是說,如果對齊了,一次就可以完成,不對齊,就可能多次才能完成。這樣,只要你在結構體里對象之間能處理好對齊,你的數據就能操作得很快。
2. 函數 posix_memalign() 使用說明
功能:返回size字節的動態內存,預對齊內存的分配。posix_memalign函數的用法類似於malloc的用法,由posix_memalign分配的內存空間,需要由free釋放。
頭文件:#include <stdlib.h>
函數原型:int posix_memalign (void **memptr,size_t alignment,size_t size);
參數:
- memptr 分配好的內存空間的首地址
- alignment 對齊邊界,Linux中,32位系統是8字節,64位系統是16字節
- size 指定分配size字節大小的內存
返回值:調用posix_memalign( )成功時會返回size字節的動態內存,並且這塊內存的地址是alignment的倍數。參數alignment必須是2的冪,還是void指針的大小的倍數。返回的內存塊的地址放在了memptr里面,函數返回值是0。
調用失敗時,沒有內存會被分配,memptr的值沒有被定義,返回如下錯誤碼之一:
EINVAL:參數不是2的冪,或者不是void指針的倍數。
ENOMEM:沒有足夠的內存去滿足函數的請求。
使用案例
int main(){
sc_pool_t *pool = sc_create_pool(1024);
int i=0;
for(i=0;i<10000;i++){
sc_pcalloc (pool,800 * 10);
}
sleep(5);
sc_destroy_pool (pool);
sleep(5);
}
下載地址
該內存分配器的源代碼地址如下:github