C++中内存的动态分配与管理永远是一个让C++开发者头痛的问题,本文通过对C++中内存的动态分配释放的基本原理的介绍,让读者朋友能对C++中的内存的动态分配与释放有较为深入的理解,从而更好驾驭C++程序。
1. 函数(Function)
(1) operator new function
1 2 |
void * ::operator new(size_t); //Global void * class-name::operator new(size_t); //Class |
上面是C++中operator new function的原型,一个是全局类型的,一个的类成员类型的。全局类型的operator new函数在下面两种情况下被调用:一种是在分配C++内建(built-in)类型的动态内存时,一种是在分配用户没有自己定义operator new成员函数的用户自定义类型的动态内存时。 如果用户在自己定义的类型中,定义了operator new函数,那么用户在用new申请该类型的动态内存时, 便会调用该类型的成员函数operator new, 而不是全局的operator new。
另外,我们注意到,上面的原型中函数的返回值为void *类型, 第一个参数为size_t类型,这个是C++编译器要求的,如果要自己重载operator new函数,返回值必须为void* 类型,第一个参数必须为size_t类型,否则,编译器会返回如下错误信息:
1 |
error: ‘operator new’ takes type ‘size_t’ (‘unsigned int’) as first parameter |
这里需要注意的一点是,我们可以利用operator new function可以重载的特点,可以通过参数传入一些额外的信息,来调试程序,检测内存泄露等。比如,我们可以像下面这样重载,传入调用处的行号,函数名,这样就可以跟踪内存的分配使用情况:
1 2 3 4 5 |
void * operator new(size_t unSize, int nLine, const char * pFunc) { prinft("Line: %d, Func: %s, allocate %u byte(s)\n", nLine, pFunc, unSize); return malloc(unSize); } |
(2) operator delete function
1 2 |
void operator delete( void * ); void operator delete( void *, size_t ); |
上面是operator delete function的原型。operator delete function也有全局的和类成员的两种。这里需要注意,一个类只能有一个operator delete function做为其成员函数,而且必须为上面两种中的其中一种,没有其它的形式。如果一个类实现了自己的operator delete function成员函数,那么在释放该类型的内存时,编译器便会调用成员operator delete function, 而不是全局的。
上面的两种原型,第一种,在调用的时候,编译器会把要释放的内存的首地址传入,第二种,在调用的时候,编译器会把要释放的内存的首地址和大小都传入。因此,可以利用这一特性,如果我们在基类中实现第二种形式的operator delete function的成员函数,那么便可以用之来释放子类类型的内存(具体参考最后面的例子)。
2. 运算符(Operator)
(1) new operator
1 2 |
[::] new [placement] new-type-name [new-initializer] [::] new [placement] ( type-name ) [new-initializer] |
注:上面的’[]‘表示在其中的部分是optional(可选的)
上面是new operator的原型。在C++中,动态内存的分配,通常都是调用new operator来完成的,利用new operator来分配动态内存,编译器要做下面两项工作:
- a. 调用operator new function分配内存(allocate the memory)
- b. 调用构造函数(call the constructor)来进行初始化
下面来说一说new operator的原型中各部分到底是干什么的:
placement: 如果你重载了operator new function, placement可以用来传递额外的参数
type-name: 指定要分配的内存的类型,可以是内建(built-in)类型,也可以是用户自定义类型
new-initializer: 指定对分配后内存的初始化的参数,也就的构造函数的参数 。这里需要注意一点,在分配一个对象的数组类型的内存时,不能够指定初始化参数;换言之,要想分配一个对象的数组类型的内存,该对象必须有缺省构造函数
(2) delete opeartor
1 2 |
[::] delete cast-expression [::] delete [ ] cast-expression |
上面是delete operator的原型,第一种用来释放普通的对象(包括内建类型)类型的内存,第二种用来释放对象的数组类型的内存。在C++中,用new operator分配的动态内存,必须调用delete operator来释放,通常用delete operator释放内存编译器要做下面两项工作:
- a. 调用对象析构函数来析构对象
- b. 调用operator delete function来释放内存(deallocate the memory)
3. 关于new/delete使用过程中一些需要注意的点
(1)如何区别operator new/delete function 与 new/delete operator ?
通过上面的讲述,不难看出,我们分配/释放动态内存,调用的是new/delete operator, 而在调用new/delete的过程中,编译器会自动调用operator new/delete function来完成实际的内存分配/释放的工作
(2) 用delete operator去释放一块不是由new operator释放的内存,结果是不可预料的,因此,切记,operator new与operator delete一定要配对使用,这是写好程序的基础
(3) new operator调用失败会抛出std::bad_alloc异常,前提是你没有自己重载对应的operator new function;delete operator失败,常见的原因是多次delete同一块内存
(4) 如果一块内存被delete后,再对它解引用(Dereference),结果也是不可预测的,很可能导致程序崩溃
(5) delete一个空(NULL)指针是安全的,没有任何害处的
(6) 类成员类型的operator new/delete函数必须为静态(static)函数,因此它们不能为虚函数(virtual function),也遵守public, protected, private的访问权限控制
4. 关于上面所讲的内容的一些例子:
程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#include <stdio .h> #include <stdlib .h> void * operator new(size_t unSize) { printf("operator new called\n"); return malloc(unSize); } void * operator new(size_t unSize, int nLine, const char * pFunc) { printf("operator new called, line: %d, func: %s\n", nLine, pFunc); return malloc(unSize); } void operator delete(void * pMem) { printf("delete1\n"); free(pMem); } class A { public: A(int a = 0) : _a(a) { printf("constructor called\n"); } { printf("~A()\n"); } static void operator delete(void * pMem, size_t unSize) { printf("delete2: %u\n", unSize); free(pMem); } private: int _a; }; class B: public A { public: ~B() { printf("~B()\n"); } int _b; int _bb; }; int main() { A * pA = new A(10); printf("#######\n"); A * pB = new (__LINE__, __func__) B(); printf("#######\n"); A * szA = new A[10]; printf("#######\n"); delete pA; printf("#######\n"); delete pB; printf("#######\n"); delete [] szA; printf("#######\n"); char * pC = NULL; delete pC; } </stdlib></stdio> |
运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
wuzesheng@wuzesheng-ubuntu:~/Program$ ./a.out operator new called constructor called ####### operator new called, line: 68, func: main constructor called ####### operator new called constructor called constructor called constructor called constructor called constructor called constructor called constructor called constructor called constructor called constructor called ####### ~A() delete2: 8 ####### ~B() ~A() delete2: 16 ####### ~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A() ~A() delete1 ####### delete1 |
上面的程序很简单,我在这里不做过多的解释,感兴趣的朋友可以自己分析一下。
通过我上面的讲解,相信大多数朋友应该对C++中内存的动态分配与释放有了较为深入的理解。后续我还有可能写一些关于C++中内存管理的文章,只有把本文所讲的内容与后续的内存管理的一些常见的方法结合起来,我们才写出更加健壮的C++程序。欢迎读者朋友留言一起交流!
from:http://www.wuzesheng.com/?p=840