上古程序員
640K
- 在 <font color=red>90年代以前</font>, 操作系統(tǒng)被 <font color=red>M-DOS</font>統(tǒng)治, 在當時的硬件環(huán)境下OS只能看到 <font color=red>1MB</font>的內(nèi)存, 但對于程序員來說, 能利用的空間只有 <font color=red>前面的640kb</font>, 雖然可用的內(nèi)存很少, 但也涌現(xiàn)了很多優(yōu)秀的軟件
錙銖必較
- 所以當時的程序員對內(nèi)存的重視計較到1個字節(jié), 隨著工業(yè)技術(shù)和軟件技術(shù)的進步, 如今的內(nèi)存已經(jīng)是海量的,這就出現(xiàn)一個新的問題--還有必要管理內(nèi)存嗎?
內(nèi)存管理的層面
- 嚴格來說, 內(nèi)存被os管理, 而且已經(jīng)管理的很嚴謹高效, 作為上層的程序員了解內(nèi)存的管理流程對自己的技術(shù)會有一個巨大的提升
memory primitives
內(nèi)存的層次

20.png
從圖中可以看到, c++程序員處于的位置是最上層的 applications
用的最多的是new, new[]
如果用容器, 則內(nèi)存基本不用管理
其次也可以調(diào)用malloc
至于最底層的os的api, 沒有可移植性
它之間的調(diào)用關(guān)系如上圖的箭頭所示
函數(shù)屬性
| 分配 | 釋放 | 所屬 | 重載 |
|---|---|---|---|
| malloc | free | c函數(shù) | no |
| new | delete | c++表達式(==new操作符==) | no |
| ::operator new() | ::operator delete() | c++函數(shù) | ==yes== |
| allocator<T>::allocate() | allocator<T>::deallocate() | STL分配器 | 可以自己設(shè)計搭配容器 |
auto p_1 = malloc(sizeof(int));
free(p_1);
stringstream* p_b = new stringstream();
delete p_b;
void* p_c = ::operator new(sizeof(int));
::operator delete(p_c);
/**
STL的分配器必須創(chuàng)建對象調(diào)用成員方法
但實際管理的內(nèi)存是統(tǒng)一的
所以 allocate和deallocate時候, 雖然對象不是同一個
但沒有問題
而且 allocate的時候, 第2個參數(shù)(VC6中必傳)void*可以不傳
deallocate的時候必須告訴分配器還什么指針(p_d)以及還多大(1)
第2個參數(shù)要和allocate時第1個參數(shù)一致
*/
int* p_d = allocator<int>().allocate(1); //1個int
allocator<int>().deallocate(p_d,1);
new的初步探究
new的工作流程
- c++的程序員基本都會用 <font color=red>new</font>來為對象分配一個 <font color=red>堆內(nèi)存</font>, 并且 <font color=red>new會調(diào)用對應(yīng)的構(gòu)造函數(shù)</font>, 構(gòu)造函數(shù)是用來初始化對象的, 所以總結(jié)出new的功能是:
- 在堆中分配一塊內(nèi)存
- 調(diào)用對應(yīng)的構(gòu)造函數(shù)
調(diào)用形式:
Object* obj = new Object(v1, v2)
new實際的工作(偽代碼):
try{
void* obj = operator::new(sizeof(Object));
Object* obj_p = static_cast<Object*>(obj);
obj_p->Object(v1,v2); // 只有編譯器可以這樣調(diào)用(但vc中可以)
}cache(std::bad_alloc){
// 分配內(nèi)存失敗
}
根據(jù)上面的內(nèi)存函數(shù)的調(diào)用流程, 所以 operator::new會調(diào)用malloc, 下面用MSVC測試一下:
測試new的調(diào)用流程

21.png

22.png

23.png

24.png
struct A{
int a;
int b;
A(){}
A(int _a, int _b): a(_a), b(_b){}
};
int main(int arg, char** args){
A* tmp_a = new A(2, 8);
delete tmp_a;
}
/**
上面的4張圖是在MSVC中反匯編的運行時代碼
從第1張圖可以看出, new 調(diào)用了operator new
從第3張圖可以看出, operator new內(nèi)部調(diào)用了malloc
operator new的函數(shù)從VC的匯編大致可以得出長這樣:
void* __CRTDECL operator new(size_t const size){
for(;;){
if(void* const block = malloc(size))
...
}
}
ps: 實際上編譯器是在 new的地方調(diào)用了對應(yīng)的構(gòu)造函數(shù), 并不是在new的內(nèi)部
new只是編譯器識別的一個標識符, 并不是函數(shù), 編譯器看到new后會malloc, 然后調(diào)用構(gòu)造函數(shù)
VS2019可以看到 operator new的源碼:
*/
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
/**
上面的 operator new的作用是 調(diào)用malloc分配內(nèi)存
當malloc成功后直接返回
當malloc失敗后:
并不會再次 malloc
而是調(diào)用 _callnewh() new_handler()
這個函數(shù)的作用是 向自己定義的函數(shù)索取內(nèi)存
所以 new_handler可以理解為釋放一些緩存
調(diào)用完new_handler后, 可能釋放了內(nèi)存, 這個時候再嘗試調(diào)用malloc獲取內(nèi)存
*/
delete的初步探究
工作流程

25.png

26.png

27.png

28.png

29.png
using namespace std;
struct A {
int a;
int b;
A() {}
A(int _a, int _b) : a(_a), b(_b) {}
~A() {}
};
int main(int arg, char** args) {
A* tmp_a = new A(2, 8);
delete tmp_a;
}
析構(gòu)的另一種調(diào)用方式
struct A {
int a;
int b;
A(int _a, int _b) : a(_a), b(_b) {}
};
int main(int arg, char** args) {
A* tmp_a = new A(2, 8);
/**
直接利用指針調(diào)用A的析構(gòu)函數(shù), 標準規(guī)格是允許的
在 19_stl.md中的 分類對算法的影響--case3 中也這樣用到過
*/
tmp_a->~A();
/**
雖然調(diào)用了析構(gòu)函數(shù), 但事實上tmp_a的空間并沒有釋放
所以可以訪問tmp_a
ps: 如果是delete tmp_a; 就不能會訪問
*/
cout << tmp_a->a << endl;
/**
在A中也沒有定義析構(gòu)函數(shù)函數(shù), 在上小節(jié)的 工作流程中的
最后1張圖提出了1個問題, 如果沒有手動寫出析構(gòu)函數(shù), 那編譯器
會不會生成默認的析構(gòu)函數(shù), 但這里測試的編譯器至少在調(diào)用時生成了
在以前學習類的時候, 說過1個類定義了如果沒有在任何地方用到就不會
生成默認的幾個成員函數(shù), 這里至少提高到不顯示調(diào)用就不會生成
*/
}
array new/delete
語法
struct A {
int a;
int b;
A() {
cout << "A() : this:" << this << endl;
}
A(int _a, int _b) : a(_a), b(_b) {
cout << "A(int,int): this: " << this << endl;
}
~A() {
cout << "~A(): this: " << this << endl;
}
};
int main(int arg, char** args){
//A* tmp = new A[2](2,6); //_code_a
A* tmp = new A[2]; //_code_b
//delete tmp; //_code_c
delete[] tmp; //_code_d
cout << "over\n"; //_code_e
}
/**
_code_a 本身想調(diào)用A的有參構(gòu)造, 但語法不成立
_code_b 用了array new, 內(nèi)部會調(diào)用malloc分配2個A大小的空間
然后調(diào)用2次A默認無參的ctor
_code_c
cl.exe(MSVC的c++編譯器, 這里用的是2019)編譯運行后runtime error, 但會調(diào)用1次tmp[0]的析構(gòu)函數(shù)
g++編譯運行后, 沒有問題, main函數(shù)正常結(jié)束, 也只會調(diào)用1次tmp[0]的析構(gòu)函數(shù)
_code_d會調(diào)用2次析構(gòu)函數(shù), 是正確的合理的寫法
先調(diào)用tmp[1], 再調(diào)用tmp[0]
new A[2] 內(nèi)部會調(diào)用malloc分配2個A大小的空間, 返回的是 A*
new A 內(nèi)部會調(diào)用malloc分配1個A大小的空間, 返回的是 A*
但最后用 delete tmp搭配new A[2] 時出現(xiàn)錯誤, 說明new A和new A[]
的過程是不一樣的, 至少結(jié)構(gòu)是不一樣的, 這留到后面的malloc再說
對于_code_c, 在g++的環(huán)境下, 可以通過, 但并不適合所有的類
當1個類的成員變量有指針時, _code_c搭配_code_b, 就會有內(nèi)存泄露
因為數(shù)組在釋放時, 并沒有調(diào)用所有元素的析構(gòu)函數(shù)
并且, 這里面有1個結(jié)構(gòu)上的問題, 后面再說
ps: 如果A沒有手動寫出析構(gòu)函數(shù), _code_c這一行在VC編譯后運行沒有問題, 但同樣可能有內(nèi)存泄露
*/
placement new
概念
- 標準不允許直接用指針調(diào)用構(gòu)造函數(shù), 但 <font color=red>給出了另一種調(diào)用語法</font>, 可以 <font color=red>在現(xiàn)有的對象的空間中調(diào)用構(gòu)造函數(shù)</font>, 它不會創(chuàng)建新的空間
struct A {
int a;
int b;
A() {
cout << "A() : this:" << this << endl;
}
A(int _a, int _b) : a(_a), b(_b) {
cout << "A(int,int): this: " << this << endl;
}
~A() {
cout << "~A(): this: " << this << endl;
}
};
int main(){
A* tmp = new A[2];
auto _idx = tmp;
for(int i = -1; ++i < 2;){
/**
利用placement new的語法, 手動調(diào)用A的構(gòu)造函數(shù)
但不會創(chuàng)建新對象, 就是說一個一個遍歷數(shù)組,
通過指針訪問對應(yīng)元素的構(gòu)造函數(shù)
下一節(jié)的 operator new第2個參數(shù) 會說明 placement new的實現(xiàn)機制
*/
new(_idx++)A(2,75);
}
delete[] tmp;
}
operator new第2個參數(shù)
int main(int arg, char** args) {
A* a = new A();
auto result = operator new(sizeof(A), a);
cout << a << endl;
cout << result << endl;
//a和result的地址一樣, 其實這里可以找到operator new的源碼
}
#define __PLACEMENT_NEW_INLINE
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _Post_satisfies_(return == _Where)
inline void* __CRTDECL operator new(size_t _Size, _Writable_bytes_(_Size) void* _Where) noexcept
{
(void)_Size;
return _Where; //如果有第2個參數(shù)則直接返回第2個參數(shù)
}
探究operator new
int main(int arg, char** args) {
A* a = new A();
new(a)A(27, 224);
void* b = ::operator new(sizeof(A));
cout << b << endl;
cout << a->a << endl;
cout << a->b << endl;
}
/**
對于這樣的調(diào)用, 匯編代碼如下:
*/

30.png

31.png

33.png

34.png
重載內(nèi)存函數(shù)
內(nèi)存分配的過程

35.png
從圖中可以看出, 當出現(xiàn)
new Foo(x)
后, 編譯器會查看 Foo 這個類有沒有 實現(xiàn) operator new(size_t)的static函數(shù)
如果有就會調(diào)用Foo這個版本的operator new, 如果沒有就調(diào)用全局的::operator new
delete也是如此
所以可以為一個類單獨實現(xiàn)operator new和operator delete, 也可以重載全局的operator new/delete
但很少這么做, 因為全局的版本是照顧所有的類
重載::operator new/delete
- 上面說了, 重載 <font color=red>全局的operator new/delete</font>影響深遠, 但可以重載, 方法是在 <font color=red>另1個==namespace==中聲明和全局版本相同的函數(shù)簽名</font>
using namespace std;
struct A {
int a;
int b;
A() {
cout << "A() : this:" << this << endl;
}
A(int _a, int _b) : a(_a), b(_b) {
cout << "A(int,int): this: " << this << endl;
}
~A() {
cout << "~A(): this: " << this << endl;
}
};
////////////////////////////
////// 以下4個函數(shù)就在當前的文件中聲明定義
inline void* operator new(size_t len) {
return malloc(len);
}
inline void* operator new[](size_t len) {
return malloc(len);
}
inline void operator delete(void* ptr) {
return free(ptr);
}
inline void operator delete[](void* ptr) {
return free(ptr);
}
int main(int arg, char** args) {
A* a = new A(); //調(diào)用到上面自定義的operator new
void* b = ::operator new(sizeof(A)); // 雖然指定的是::operator new, 但還是調(diào)用到上面自定義的版本
// 對于a是正確的釋放, 先調(diào)用a的析構(gòu)函數(shù), 再調(diào)用上面的operator delete
delete a;
// 對于b, 釋放的操作應(yīng)該顯示調(diào)用operator delete
/// 這里用delete后, 并沒有調(diào)用A的析構(gòu)函數(shù), 而是直接調(diào)用了 operator delete
///// 可見編譯器對于 delete void* 的時候, 是直接轉(zhuǎn)換為 opreator delete
delete b;
}
重載類本身的operator new/delete
using namespace std;
struct A {
A() {
cout << "A() : this:" << this << endl;
}
~A() {
cout << "~A(): this: " << this << endl;
}
/**
這個函數(shù)寫不寫static都會被編譯器做成static
*/
static void* operator new(size_t len) {
return malloc(len);
}
static void operator delete(void* ptr, size_t len) {
return free(ptr);
}
};
int main(int arg, char** args) {
//調(diào)用A::operator new版本
A* a = new A();
// 調(diào)用全局的operator new, 如果想調(diào)用A的版本, 則顯示調(diào)用 A::operator new()
void* b = operator new(sizeof(A));
A* c = ::new A(); //調(diào)用全局的operator new
void* d = ::new A(); //調(diào)用全局的operator new
delete a; //先調(diào)用a的析構(gòu)函數(shù),再調(diào)用a的operator delete
delete b; //直接調(diào)用了全局的operator delete
::delete c;
::delete d;
}
本質(zhì)就是重載new
- new是 <font color=red>關(guān)鍵字</font>, 編譯器看到new, 會轉(zhuǎn)換成 <font color=red>operator new</font>的調(diào)用, 所以new也可以理解為操作符(==畢竟用operator修飾的==)
A* a = new A(); //轉(zhuǎn)換成 A* a = ::operator new(sizeof(A));
// 如果有A自己定義了 operator new, 則會轉(zhuǎn)換成 A* a = operator new(sizeof(A));
// 接著調(diào)用A的無參ctor
/// 也就是說這句代碼完整的應(yīng)該是這樣 A* a = new(sizeof(A)) A();
/////// 但編譯器不允許這么寫
/**
前面在說placement new的時候:
new(a)A(2,2); //A(2,2)表示A有一個2參數(shù)的ctor, new(a)中的a是一個void*
本質(zhì)是調(diào)用了 operator new(size_t, void*)的函數(shù), 那就說明:
可以為 operator new 重載不同參數(shù)的operator new函數(shù)
*/
struct A {
A() {
cout << "A() : this:" << this << endl;
}
~A() {
cout << "~A(): this: " << this << endl;
}
// 最原始的operator new
void* operator new(size_t len) {
return malloc(len);
}
// 就是placement new(前面說過placement new不會創(chuàng)建新的內(nèi)存)
void* operator new(size_t len, void* t) {
return t;
}
// 為operator new重載自己的版本
static void* operator new(size_t len, const char* str) {
void* mem = malloc(len);
cout << "str" << endl;
return mem;
}
static void operator delete(void* ptr, size_t len) {
return free(ptr);
}
};
int main(int arg, char** args) {
// 調(diào)用operator new(size_t)
A* a = new A; //或 new A()
// 調(diào)用 operator new(size_t, void*)
///很可能會出問題, 因為*args并不是A*的指針
/// 所以調(diào)用ctor時,如果ctor內(nèi)部訪問了成員變量, 就會有問題
A* b = new((void*)*args) A; // 無參的ctor可以不用A()
// 調(diào)用operator new(size_t, const char*)
A* c = new("hello") A; // 無參的ctor 可以不用 A()
/**
重載operator new時, 第1個參數(shù)必須是size_t, 并且用new 調(diào)用時不能傳遞第1個參數(shù)
*/
}
關(guān)于多參new對應(yīng)的delete
- 上面重載了參數(shù)不一的new(==operator new==), 那是不是要重載對應(yīng)的delete呢?
- 理論上delete的工作 <font color=red>只是為了釋放內(nèi)存</font>, 所以只需要一個==指針==就可以了, 但c++在語法上支持 <font color=red>多參的delete重載</font>, 但delete的調(diào)用格式只有 ==delete type==, 所以關(guān)于重載的delete版本什么時候被調(diào)用, 并不是用戶手動調(diào)用, 而是對應(yīng)版本的==ctor發(fā)生異常后, 會由crt來調(diào)用對應(yīng)的delete
struct A{
A():{} //_ctor_a
A(int):{throw 2;} //_ctor_b
static operator new(size_t len){ .. } //_new_a
static operator new(size_t len, int e){ .. } //_new_b
static operator delet(void* ptr){ ... } //_del_a
static operator delet(void* ptr, int e){ ...} //_del_b
};
int main(int arg, char** args){
A* a = new A(); //_new_a, _ctor_a
A* b = new(444)A; //_new_b, _ctor_a
A* c = new A(2); //_new_a, _ctor_b
//_ctor_b有異常但卻沒有調(diào)用 _del_a
A* d = new(42)A(75); //_new_b, _ctor_b
//有異常, 但卻沒有調(diào)用_del_b
/**
也就是說重載的operator delete就是為了處理new的異常, 但上面卻沒有調(diào)用
在g++早期的版本, 上面是會調(diào)用對應(yīng)的 _del_a和_del_b, 現(xiàn)在不會了, VC也不會
事實上, new的異常處理有專門的語法格式
*/
}
class A {
public:
A() try:a(test()) {
cout << "A()\n";
}catch (...) {
// 這里可以訪問當前對象的成員, 所以在這里釋放
if(b){
delete b;
b = nullptr;
}
cout << "A catch ...\n";
}
int test() {
b = new int();
*b = 64;
throw 2;
return 4;
}
~A() {
if(b){
delete b;
b = nullptr;
}
cout << "A destructor\n";
}
private:
int a;
int* b;
};
int main(int arg, char** args) {
A a;
return 0;
}
利用operator new實現(xiàn)類似java中的satic方法
- 在java中, 一個類最先調(diào)用的函數(shù)是 static方法(==不屬于對象==),c++中原本是沒有的, 但利用operator new可以模擬出類似的效果
public class A{
static{
System.out.println("fir");
}
static public void main(String[] args){
System.out.println("two");
}
}
/**
上面最先調(diào)用的是 static{}, 下面是c++的實現(xiàn)
*/
struct A{
static void do_sth{
...
}
static operator new(size_t len){
// do something
static once_flag o;
std::call_once(once_flag, do_sth);
return ::operator new(len);
}
};
/**
其實上述的實現(xiàn)和java中的static靜態(tài)函數(shù)并不等價,
java中的static一定在main前面調(diào)用, 并且不存在線程競爭
而cpp的實現(xiàn), 是程序已經(jīng)運行起來, 并且可能存在線程競爭
*/
struct A {
friend static void call();
private:
static void s_static_func() {
n = new int(2);
this_thread::sleep_for(chrono::seconds(2));
cout << "before main\n";
}
static int* n;
};
int* A::n;
static void call() {
A::s_static_func();
}
auto lam = [](void) -> int{
call();
return 0;
}();
int main(int arg, char** args) {
cout << "main" << endl;
}
new handler
注冊
- 當 <font color=red>operator new</font> 調(diào)用==malloc==后, 發(fā)現(xiàn)沒有內(nèi)存, 會調(diào)用==new handler==, 所以new_handler的作用應(yīng)該是釋放一些不必要的內(nèi)存
- 可以注冊自己的new handler
/**
void(__cdecl*)(void) __cdecl是標準的c調(diào)用約定(函數(shù)從右往左壓棧)
using new_handler = void(__CLRCALL_PURE_OR_CDECL*)();
/**
設(shè)置新的new handler, 返回舊的new handler
*/
_CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler) noexcept;
_NODISCARD _CRTIMP2 new_handler __cdecl get_new_handler() noexcept;
*/
void __cdecl mem_release(void){
}
int main(int arg, char** args){
auto old_handler = set_new_handler(mem_release);
}
設(shè)計內(nèi)存管理機制[^ann_1]
為什么要自己設(shè)計
c++中new的底層調(diào)用了c的malloc, 關(guān)于malloc會在后面探究, 這里先大致提一下:
malloc本身很復(fù)雜, 效率也很高, 它本身就是一個內(nèi)存管理器, 也可以稱為內(nèi)存池
malloc在設(shè)計上會對給出的內(nèi)存附加額外的內(nèi)存
當然用戶是不知道的
細心的程序員可能想到了free
free函數(shù)只接收1個指針, 但為什么就可以準確釋放呢?
原因很可能是malloc的約定, 這個到后面細說
基于malloc的特點, 對容器來說, 特別是元素相鄰的容器(如vector)
如果直接用malloc, 則每次分配內(nèi)存后都會附加其他內(nèi)存
由于每次push_back, 調(diào)用malloc后元素就不是緊緊相鄰的, 這樣vector管理元素的復(fù)雜度就大大提升
所以要實現(xiàn)一個沒有額外內(nèi)存的管理器提供給vector
思路:
降低malloc調(diào)用次數(shù)
目的是減少額外內(nèi)存
有效利用手上的空間
下面開始就是一步一步從最簡單的分配器開始, 一直演進到gcc中著名的內(nèi)存池
內(nèi)存管理第0步
- 最重要的就是 <font color=red>接手new</font>, 從前面的學習中已經(jīng)知道了, 可以為==類重載new==, 這樣 <font color=red>申請內(nèi)存的操作就到了自己的手中</font>, 下面開始為 <font color=red>1個單一的類設(shè)計內(nèi)存管理</font>
內(nèi)存管理單個類
分析:
由于malloc有碎片, 所以為了減少malloc的調(diào)用, 但又不得不從malloc拿內(nèi)存, 所以:
要拿就拿夠
思路:
一次性向malloc索取一大塊內(nèi)存, 放到自己的手中
當類new的時候, 先看手上有沒有
有就給出去指針
沒有就向malloc索取
當delete時, 將內(nèi)存再回收到自己的手中
索取的內(nèi)存用鏈表來管理
#define _MEM_ERROR_0 -1
#define _NEW_CODE_ \
if (size <= 0)\
throw _MEM_ERROR_0;\
return A::alloc(size);
#define _DEL_CODE_ \
if(ptr) \
return dealloc(ptr);
class A {
public:
void* operator new(size_t size) {
_NEW_CODE_
}
void* operator new[](size_t size) {
_NEW_CODE_
}
void operator delete(void* ptr) {
_DEL_CODE_
}
void operator delete[](void* ptr) {
_DEL_CODE_
}
private:
static void make_list(A* begin, A* const end) {
while (begin != end)
(begin->next = begin + 1, ++begin);
begin->next = nullptr;
}
static void* _malloc_chunk(size_t len) {
size_t need_mem = A::chunk * len;
size_t mod = need_mem / sizeof(A);
A* need = static_cast<A*>(malloc(need_mem));
// 取頭尾指針, 包含尾指針
A::make_list(need, reinterpret_cast<A*const>(need + mod - 1));
return need;
}
static void* alloc(size_t len) {
A* result = A::free_mem_start;
if (!result)
result = A::free_mem_start = static_cast<A*>(A::_malloc_chunk(len));
A::free_mem_start = A::free_mem_start->next;
return static_cast<void*>(result);
}
static void dealloc(void* ptr) {
//將 已經(jīng)調(diào)用過析構(gòu)函數(shù)的廢棄的ptr指向當前鏈表的頭部
static_cast<A*>(ptr)->next = A::free_mem_start;
//再將頭部的指針指向ptr
A::free_mem_start = static_cast<A*>(ptr);
/*
這里無法將指針還給os(free), 因為無法確定ptr是不是malloc給出的合理指針
*/
}
private:
static constexpr int chunk = 4;
static A* free_mem_start;
// 相當于鏈表中的節(jié)點
struct {
A* next;
int data;
};
};
A* A::free_mem_start = nullptr;
int main(int arg, char** args) {
{
A* a = ::new A;
A* b = ::new A;
A* c = ::new A;
cout << a << "\n";
cout << b << "\n";
cout << c << "\n";
}
cout << "***************************\n";
{
for (int i = -1; ++i < 11;) {
A* a = new A;
cout << a << endl;
}
}
}
/**
00DEFF40
00DEF990
00DEFB18
***************************
00DEC130
00DEC138
00DEC140
00DEC148
00DF24B8
00DF24C0
00DF24C8
00DF24D0
00DF2468
00DF2470
00DF2478
前3個直接調(diào)用的全局的operator new, 所以是malloc分配, 所以3個堆內(nèi)存是不相鄰的
代碼中設(shè)置的chunk為4, 所以每4次分配內(nèi)存
相鄰的4次之間是緊緊相連的(A對象大小8個字節(jié))
存在的問題:
為了實現(xiàn)鏈表, 在A的類型中加入了一個額外的指針next
分析可以知道 在鏈表中的內(nèi)存就只有2種狀態(tài):
外界在用,即沒在鏈表中
外界沒有在用, 即在鏈表中
而A中的data和next實際就是這2種狀態(tài), 外界在用, 則next是無效的(data有效), 外界沒有用, 則next有效(data無效)
所以可以考慮 data, next共用同一塊內(nèi)存(union)
修改:
將 A中的struct 直接修改為union, 其他地方不要改, 測試如下:
*/
00DC7E28
00DC7E58
00DCBCD0
***************************
00DD1FC0
00DD1FC4
00DD1FC8
00DD1FCC
00DCBD00
00DCBD04
00DCBD08
00DCBD0C
00DC6150
00DC6154
00DC6158
/**
改為union后, A大小是4個字節(jié), 所以每個元素大小是4,并且是相鄰的
現(xiàn)在測試代碼再改一下
*/
int main(int arg, char** args){
for (int i = -1; ++i < 11;) {
A* a = new A;
cout << a << endl;
delete a;
}
}
/**
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
發(fā)現(xiàn)a的地址始終是同一個, 說明是不斷的從池中拿的
*/
上述實現(xiàn)的bug
問題:
1. 只適用于 new/delete, 如果是 array new/array delete就有問題
2. 如果A的大小 < void*, 則會有額外有多余的內(nèi)存, 但這是合理的, 雖然看起來浪費了, 但卻在靈活使用手上的內(nèi)存
無法寫出析構(gòu)函數(shù):
原因是不可能找到最初的malloc的地址指針
原因:
alloc 函數(shù)中:
A::free_mem_start = A::free_mem_start->next _code_a
這句代碼是以 1個sizeof(A)為單位往外給內(nèi)存, 也就是alloc函數(shù)參數(shù)len的大小是sizeof(A)
但如果是 new A[2], 那么傳遞到alloc時, len就是 2*sizeof(A)為8
此時_code_a中的 free_mem_start應(yīng)該移動2次
即: A::free_mem_strat = A::free_mem_start->next->next
相應(yīng)的 在dealloc函數(shù)中, 如果還的是 A[2] 這個數(shù)組, 應(yīng)該先在delete[]中將數(shù)組中的2個元素串起來, 再傳給dealloc
但可惜的是 delete[] arr_obj, 不會將arr_obj整個數(shù)組的大小傳遞過去
所以如果牽扯到 new A[] 和 new A的交叉使用, 這個內(nèi)存池是不可以的, 而且無法將內(nèi)存歸還給操作系統(tǒng)
解決:
關(guān)于第1個交叉使用的問題, 可以在內(nèi)存池內(nèi)部維護 N*sizeof(A) 的鏈表, 這個也是后面gcc中一個分配器的實現(xiàn)
第2個歸還os的問題, 當前這種設(shè)計是不支持的, 但并不是所謂的內(nèi)存泄露, 因為所有的內(nèi)存都在自己的手上
所以上面的版本只能對 new A()使用, 應(yīng)該刪除掉 new[]和delete[]
進化到版本2(通用到各個類)
/**
就是把內(nèi)存相關(guān)的部分抽取出來
*/
template<int _N>
constexpr size_t _align_min() {
static_assert(_N > 0, "通過");
return (size_t)_N < sizeof(void*) ? sizeof(void*) : _N;
}
template<int _N>
constexpr size_t _check_chunk() {
static_assert(_N > 0, "通過");
return _N;
}
template<
int Num,
int chunk = 4,
size_t _unit = _align_min<Num>(),
size_t _chunk = _check_chunk<chunk>()>
class _alloc_ {
public:
void* alloc() {
_Node* result = this->head;
if (!result) {
cout << _chunk << " " << _unit << endl;
result = this->head = static_cast<_Node*>(::operator new(_chunk * _unit));
cout << "malloc address: " << result << endl;
for (; result != &this->head[chunk - 1];)
(result->next = result + 1, ++result);
result->next = nullptr;
result = this->head;
}
this->head = this->head->next;
return reinterpret_cast<void*>(result);
}
void dealloc(void* ptr) {
cout << "回收: " << ptr << endl;
reinterpret_cast<_Node*>(ptr)->next = this->head;
this->head = reinterpret_cast<_Node*>(ptr);
}
public:
template<size_t _N>
struct _tuple_ : _tuple_<_N - 1> {
char data;
};
template<>
struct _tuple_<0> {};
using _Data = _tuple_<_unit>;
using _Data_p = typename add_pointer<_Data>::type;
union _Node{
_Data data;
_Node* next;
};
_Node* head;
};
/**
交差delete測試1
*/
int main(int arg, char** args) {
_alloc_<2> all{};
for (int i = -1; ++i < 10;){
auto p = all.alloc();
cout <<"fir:" << p << endl;
all.dealloc(p); //只回收1次
p = all.alloc();
cout <<"sec:" << p << endl;
}
}
/**
4 4
malloc address: 013BC130
fir:013BC130
回收: 013BC130
sec:013BC130
fir:013BC134
回收: 013BC134
sec:013BC134
fir:013BC138
回收: 013BC138
sec:013BC138
fir:013BC13C
回收: 013BC13C
sec:013BC13C
4 4
malloc address: 013C1F90
fir:013C1F90
回收: 013C1F90
sec:013C1F90
fir:013C1F94
回收: 013C1F94
sec:013C1F94
fir:013C1F98
回收: 013C1F98
sec:013C1F98
fir:013C1F9C
回收: 013C1F9C
sec:013C1F9C
4 4
malloc address: 013B6428
fir:013B6428
回收: 013B6428
sec:013B6428
fir:013B642C
回收: 013B642C
sec:013B642C
這個測試結(jié)果沒有問題
*/
/**
交差測試2
*/
int main(int arg, char** args) {
_alloc_<2> all{};
for (int i = -1; ++i < 10;){
auto p = all.alloc();
cout <<"fir:" << p << endl;
p = all.alloc();
cout <<"sec:" << p << endl;
all.dealloc(p); //只回收1次
}
}
/**
4 4
malloc address: 00A01FC0
fir:00A01FC0
sec:00A01FC4
回收: 00A01FC4
fir:00A01FC4
sec:00A01FC8
回收: 00A01FC8
fir:00A01FC8
sec:00A01FCC
回收: 00A01FCC
fir:00A01FCC
4 4
malloc address: 009FBD00
sec:009FBD00
回收: 009FBD00
fir:009FBD00
sec:009FBD04
回收: 009FBD04
fir:009FBD04
sec:009FBD08
回收: 009FBD08
fir:009FBD08
sec:009FBD0C
回收: 009FBD0C
fir:009FBD0C
4 4
malloc address: 009F7E28
sec:009F7E28
回收: 009F7E28
fir:009F7E28
sec:009F7E2C
回收: 009F7E2C
fir:009F7E2C
sec:009F7E30
回收: 009F7E30
也沒有問題
*/
完善版本2
template<int _N>
constexpr size_t _align_min() {
static_assert(_N > 0, "通過");
return (size_t)_N < sizeof(void*) ? sizeof(void*) : _N;
}
template<int _N>
constexpr size_t _check_chunk() {
static_assert(_N > 0, "通過");
return _N;
}
template<
int Num,
int chunk = 4,
size_t _unit = _align_min<Num>(),
size_t _chunk = _check_chunk<chunk>()>
class _alloc_ {
public:
void* alloc() {
_Node* result = this->head;
if (!result) {
cout << _chunk << " " << _unit << endl;
result = this->head = static_cast<_Node*>(::operator new(_chunk * _unit));
cout << "malloc address: " << result << endl;
for (; result != &this->head[chunk - 1];)
(result->next = result + 1, ++result);
result->next = nullptr;
result = this->head;
}
this->head = this->head->next;
return reinterpret_cast<void*>(result);
}
void dealloc(void* ptr) {
cout << "回收: " << ptr << endl;
reinterpret_cast<_Node*>(ptr)->next = this->head;
this->head = reinterpret_cast<_Node*>(ptr);
}
template<typename T>
static _alloc_& get() {
static _alloc_<sizeof(T)> mem;
cout <<"memory static alloca: " << &mem << endl;
return mem;
}
private:
template<size_t _N>
struct _tuple_ : _tuple_<_N - 1> {
char data;
};
template<>
struct _tuple_<0> {};
using _Data = _tuple_<_unit>;
using _Data_p = typename add_pointer<_Data>::type;
union _Node{
_Data data;
_Node* next;
};
_Node* head;
};
#define _US_LB_ALLOCATOR_NEW(_Cls) \
void* operator new(size_t size) { \
return _alloc_<sizeof(_Cls)>::get<_Cls>().alloc();\
}\
#define _US_LB_ALLOCATOR_DEL(_Cls) \
void operator delete(void* ptr) { \
return _alloc_<sizeof(_Cls)>::get<_Cls>().dealloc(ptr);\
}\
#define _US_LB_ALLCATOR(_Cls)\
_US_LB_ALLOCATOR_NEW(_Cls)\
_US_LB_ALLOCATOR_DEL(_Cls)
class A {
public:
_US_LB_ALLCATOR(A)
private:
int a;
int b;
};
class B {
public:
_US_LB_ALLCATOR(B)
private:
int b;
};
int main(int arg, char** args) {
for (int i = -1; ++i < 4;)
{
auto p_a = new A();
cout <<"fir p_a" << p_a << endl;
p_a = new A();
cout <<"sec:" << p_a << endl;
auto p_b = new B();
cout << "fir p_b: " << p_b << endl;
delete p_b;
p_b = new B();
cout << "sec p_b: " << p_b << endl;
delete p_b;
delete p_a;
}
}
/**
memory static alloca: 002F03C4
4 8
malloc address: 00DEC130
fir p_a00DEC130
memory static alloca: 002F03C4
sec:00DEC138
memory static alloca: 002F06E4
4 4
malloc address: 00DE6428
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC138
memory static alloca: 002F03C4
fir p_a00DEC138
memory static alloca: 002F03C4
sec:00DEC140
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC140
memory static alloca: 002F03C4
fir p_a00DEC140
memory static alloca: 002F03C4
sec:00DEC148
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC148
memory static alloca: 002F03C4
fir p_a00DEC148
memory static alloca: 002F03C4
4 8
malloc address: 00DF2508
sec:00DF2508
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DF2508
*/