C++中的自定义内存管理
1,问题:
1,new 关键字创建出来的对象位于什么地方?
1,位于堆空间;
2,有没有可能位于其它地方?
1,有;
2,通过一些方式可以使动态创建的对象位于静态存储区;
3,这个存储区在程序结束后释放;
2,new/delete 被忽略的事实:
1,new/delete 的本质是 c++ 预定义的操作符;
1,new/delete 是关键字,但本质是预定义的操作符;
2,c++ 中操作符可以重载;
2,c++ 对这两个操作符做了严格的行为定义;
1,new:
1,获取足够大的内存空间(默认为堆空间);
2,在获取的空间中调用构造函数创建对象;
2,delete:
1,调用析构函数销毁对象;
2,归还对象所占用的空间(默认为堆空间);
3,在 c++ 中能够重载 new/delete 操作符:
1,全局重载(不推荐);
1,实际工程开发中不建议这样做
2,局部重载(针对具体类型进行重载);
1,针对具体的类重载;
3,重载 new/delete 的意义在于改变动态对象创建时的内存分配方式;
1,可以将创建的对象放到其它的内存空间里面去;
4,new/delete 的重载方式:
1,代码示例:
1 // static member function 2 void* operator new(unsinged int size) // 第一步获取内存,参数表示需要获取的内存大小; 3 { 4 void* ret = null; 5 6 /* ret point to allocated memory */ // 第二步在内存中调用构造函数创建对象; 7 8 return ret; 9 } 10 11 // static member function 12 void operator delete (void* p) // p 指针指向对应的对象地址,也就是要释放的地址; 13 { 14 /* free the memory which is pointed by p */ 15 }
2,通过函数来对这两个操作符进行重载;
3,一般针对具体类来重载,所以说 new/delete 的重载函数就是类的成员函数,并且这两个重载函数默认为静态成员函数,写不写 static 都是静态成员函数;
3,静态存储区中创建动态对象编程实验:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class test 7 { 8 static const unsigned int count = 4; 9 10 static char c_buffer[]; // 本质是这里申请空间而下面只是标记使用而已; 11 static char c_map[]; 12 13 int m_value; 14 public: 15 void* operator new (unsigned int size) 16 { 17 void* ret = null; // 如果这片内存已经满了,返回空; 18 19 /* 查找在 c_buffer 里面那些位置是空闲的,可以用来创建 test 对象 */ 20 for(int i=0; i<count; i++) 21 { 22 if( !c_map[i] ) // 当前空间不可用了; 23 { 24 c_map[i] = 1; // 标记为不可用; 25 26 ret = c_buffer + i * sizeof(test); // 查找 c_buffer 这片可用内存空间的首地址,并返回这片空间; 27 28 cout << "succeed to allocate memory: " << ret << endl; 29 30 break; 31 } 32 } 33 34 return ret; 35 } 36 37 void operator delete (void* p) 38 { 39 if( p != null ) // 空指针时候什么都不处理; 40 { 41 char* mem = reinterpret_cast<char*>(p); 42 int index = (mem - c_buffer) / sizeof(test); // 得到要释放的动态对象在 c_map 中的位置; 43 int flag = (mem - c_buffer) % sizeof(test); // 这些位置必须是固定的,如果 flag 不为 0,指针则不合法; 44 45 if( (flag == 0) && (0 <= index) && (index < count) ) 46 { 47 c_map[index] = 0; // 释放这个地址,即标记这个地址可用; 48 49 cout << "succeed to free memory: " << p << endl; 50 } 51 } 52 } 53 }; 54 55 char test::c_buffer[sizeof(test) * test::count] = {0}; // 定义一块静态的内存空间,内存空间想要存储的是 test 对象,最多存储 4 个 test 对象; 56 char test::c_map[test::count] = {0}; // 标记数组,用于标记在那些位置已经创建了对象,作用是标记; 57 58 int main(int argc, char *argv[]) 59 { 60 cout << "===== test single object =====" << endl; 61 62 test* pt = new test; // 这里是在 c_buffer 里面的静态存储区当中的空间生成的; 63 64 delete pt; 65 66 cout << "===== test object array =====" << endl; 67 68 test* pa[5] = {0}; 69 70 for(int i=0; i<5; i++) 71 { 72 pa[i] = new test; 73 74 cout << "pa[" << i << "] = " << pa[i] << endl; 75 } 76 77 for(int i=0; i<5; i++) 78 { 79 cout << "delete " << pa[i] << endl; 80 81 delete pa[i]; 82 } 83 84 return 0; 85 }
1,结论:
1,new/delete 关键字是可以重载的;
2,重载的意义是改变内存的分配方式,使得动态创建的对象不再位于堆空间里面;
3,这个实验位于自定义的静态存储区里面的 c_buffer 数组当中;
2,拓展:
1,工程中可以结合不同方法来应用 new/delete 特性;
2,将本实验的方法和二阶构造法结合在一起,我们就可以创建一个类,并且规定这个类最多产生多少个对象;
3,单例模式仅仅使得一个类只有一个对象存在,而这里的方法加上二阶构造就可以诞生 n 例模式;
4,问题:
1,如何在指定的地址上创建 c++ 对象?
1,我们已经掌握了在静态存储区里面创建对象,是否可以扩展下这个方法,在任意的地址上创建对象呢?
2,通过重载 new/delete 也许就可以在指定的地址上创建对象;
5,设计思路:
1,在类中重载 new/delete 操作符;
2,在 new 的操作符重载函数中返回指定的地址;
3,在 delete 操作符重载中标记对应的地址可用;
6,自定义动态对象的存储空间编程实验:
1 #include <iostream> 2 #include <string> 3 #include <cstdlib> 4 5 using namespace std; 6 7 class test 8 { 9 static unsigned int c_count; // 动态实时做决定,所以这个地方就不能有常量; 10 static char* c_buffer; 11 static char* c_map; 12 13 int m_value; 14 public: 15 /* 动态指定想在什么类型上指定申请对象 */ 16 static bool setmemorysource(char* memory, unsigned int size) 17 { 18 bool ret = false; // 返回值为 bool 类型,告诉函数调用者,当前动态空间设置是否成功; 19 20 c_count = size / sizeof(test); // 计算传进来的空间可以创建多少对象; 21 22 ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char))))); 23 24 if( ret ) // 空间至少为 1,且标记指针合法; 25 { 26 c_buffer = memory; // 将指定空间设置到 c_buffer 上; 27 } 28 else // 一切清零; 29 { 30 free(c_map); 31 32 c_map = null; 33 c_buffer = null; 34 c_count = 0; 35 } 36 37 return ret; 38 } 39 40 void* operator new (unsigned int size) 41 { 42 void* ret = null; 43 44 /* 有指定的一个具体空间,通过各种计算和验证,看下所指定的空间上面是否可以动态创建对象,标准是 c_count 大于 0,此时意味着通过 setmemorysource() 所指定的空间是可以创建 test 对象的,则走 if 路径,否则的话,走 else 路径,通过 malloc() 函数得到一片空间; */ 45 if( c_count > 0 ) // 46 { 47 for(int i=0; i<c_count; i++) 48 { 49 if( !c_map[i] ) 50 { 51 c_map[i] = 1; 52 53 ret = c_buffer + i * sizeof(test); 54 55 cout << "succeed to allocate memory: " << ret << endl; 56 57 break; 58 } 59 } 60 } 61 else 62 { 63 ret = malloc(size); // 没有指定具体的在那个空间上生成对象时,通过 malloc 来申请默认的堆空间; 64 } 65 66 return ret; 67 } 68 69 void operator delete (void* p) 70 { 71 if( p != null ) 72 { 73 if( c_count > 0 ) 74 { 75 char* mem = reinterpret_cast<char*>(p); 76 int index = (mem - c_buffer) / sizeof(test); 77 int flag = (mem - c_buffer) % sizeof(test); 78 79 if( (flag == 0) && (0 <= index) && (index < c_count) ) 80 { 81 c_map[index] = 0; 82 83 cout << "succeed to free memory: " << p << endl; 84 } 85 } 86 else 87 { 88 free(p); // 和上面对应 89 } 90 } 91 } 92 }; 93 94 unsigned int test::c_count = 0; 95 char* test::c_buffer = null; 96 char* test::c_map = null; 97 98 int main(int argc, char *argv[]) 99 { 100 char buffer[12] = {0}; // 定义一片栈上空间,用于存放对象; 101 102 test::setmemorysource(buffer, sizeof(buffer)); 103 104 cout << "===== test single object =====" << endl; 105 106 test* pt = new test; 107 108 delete pt; 109 110 cout << "===== test object array =====" << endl; 111 112 test* pa[5] = {0}; 113 114 for(int i=0; i<5; i++) // 只有 3 个对象的空间,则后两个对象指向为 null; 115 { 116 pa[i] = new test; 117 118 cout << "pa[" << i << "] = " << pa[i] << endl; 119 } 120 121 for(int i=0; i<5; i++) 122 { 123 cout << "delete " << pa[i] << endl; 124 125 delete pa[i]; 126 } 127 128 return 0; 129 }
1,通过重载 new/delete,我们可以在任意指定的位置动态创建 c++ 对象;
7,new[]/delete[] 与 new/delete 完全不同:
1,动态对象数组创建通过 new[] 完成;
2,动态对象数组的销毁通过 delete[] 完成;
3,new[]/delete[] 能够被重载,进而改变内存管理方式;
1,这是两个新的操作符;
8,new[]/delete[] 的重载方式:
1,代码示例:
1 // static member function 2 void* operator new[] (unsigned int size) 3 { 4 rerurn malloc(size); 5 } 6 7 // static member function 8 void operator delete[] (void* p) 9 { 10 free(p); 11 }
2,通过类的静态成员函数来重载,不写 static,这两个成员函数在类中也是 静态的;
9,注意事项:
1,nwe[] 实际需要返回的内存空间可能比期望的要多;
1,需要额外的空间来保存数组的信息;
2,如数组长度信息,因为编译器要自动的为我们调用构造函数和析构函数,不保存长度信息,编译器不知道要调用多少次构造函数和析构函数;
2,对象数组占用的内存中需要保存数组信息;
3,数组信息用于确定构造函数和析构函数的调用次数;
10,动态数组的内存管理编程实验:
1 #include <iostream> 2 #include <string> 3 #include <cstdlib> 4 5 using namespace std; 6 7 class test 8 { 9 int m_value; 10 public: 11 test() 12 { 13 m_value = 0; 14 } 15 16 ~test() 17 { 18 } 19 20 void* operator new (unsigned int size) 21 { 22 cout << "operator new: " << size << endl; 23 24 return malloc(size); 25 } 26 27 void operator delete (void* p) 28 { 29 cout << "operator delete: " << p << endl; 30 31 free(p); 32 } 33 34 void* operator new[] (unsigned int size) 35 { 36 cout << "operator new[]: " << size << endl; 37 38 return malloc(size); 39 } 40 41 void operator delete[] (void* p) 42 { 43 cout << "operator delete[]: " << p << endl; 44 45 free(p); 46 } 47 }; 48 49 int main(int argc, char *argv[]) 50 { 51 test* pt = null; 52 53 pt = new test; // operator new: 4; 54 55 delete pt; // operator delete: 0x8e5d008; 56 57 pt = new test[5]; // operator new[]: 24;这里多了四个字节,用于保存数组的大小信息,因为编译器自动为我们自调用构造函数和析构函数; 58 59 delete[] pt; // operator delete[]: 0x8e5d018; 60 61 return 0; 62 }
1,new/delete 和 new[]/delete[] 是完全不同的;
2,通过重载的方式说明了它们的不同;
3,意味着在实际的工程里面,有可能在 new 中函数的内存分配方式和 delete[] 中函数内存分配方式是不一样的,因此必须成对使用,必须要匹配;
4,假设 new[] 动态创建数组是从栈上分配的空间,然后 delete 想要将空间归还到堆空间去,如果交叉使用,则意味着有可能把栈上的空间归还到堆空间上,程序会崩溃,所以要成对出现,不要交叉使用,因为它们 new/delete 和 new[]/delete[] 完全不同;
11,小结:
1,new/delete 的本质为操作符;
2,可以通过全局函数重载 new/delete(不推荐);
3,可以针对具体的类重载new/delete;
4,new[]/delete[] 与 new/delete 完全不同;
上一篇: JNA 相关问题
推荐阅读