[GeekBand] C++ 内存分布—— new和delete重载的实现及分析
本文参考文献:GeekBand课堂内容,授课老师:侯捷
:深度探索C++对象模型(侯捷译)
:网络资料: http://www.leavesite.com/geekband-cpp-5.html
http://blog.csdn.net/wudaijun/article/details/9273339
本周的课题是:“ 为上周题目中的 Fruit和Apple 添加 构造函数与 析构函数, 并在构造函数与析构函数中打印控制台信息,观察构造和析枸调用过程。然后为Apple类重载::operator new和 ::operator delete,在控制台打印信息,并观察调用结果。”
虽然其中构造与析构调用过程上次代码里已经实现了,并且比现在这份还要完善一些。但做为开场白再讲一遍比较好!
首先,先看下 类的结构。Apple 类继承自基类Fruit
1 //基类 2 class Fruit 3 { 4 public: 5 //使用自带的构造函数 6 Fruit() 7 { 8 cout << "Call Fruit Constructor.this = " <<this<< endl; 9 } 10 //打印变量内存地址 11 void print(){} 12 //虚函数的影响 13 virtual void process(){} 14 15 virtual ~Fruit() 16 { 17 cout << "Call Fruit Destructor = " << this << endl; 18 } 19 20 private: 21 int no; 22 double weight; 23 char key; 24 }; 25 26 //这里考虑自己本身的虚函数,及基类的虚函数 27 class Apple : public Fruit 28 { 29 public: 30 //使用默认的构造函数 31 Apple() 32 { 33 cout << "Call Apple Constructor.this = " << this << endl; 34 }; 35 //打印成员数据 36 void save(){} 37 virtual void process(){} 38 virtual ~Apple() 39 { 40 cout << "Call Apple Destructor.this = " << this << endl; 41 } 42 43 //测试二、抛出异常 44 static void* operator new(size_t size); 45 //测试三、没有抛出异常,此版本需要注释掉测试二 46 //static void* operator new(size_t size, const std::nothrow_t& nothrow_value); 47 48 //测试四、带有调试信息的版本,此版本需要注释掉测试二、测试三 49 //inline void* Apple::operator new(size_t size, const char* file, int line); 50 51 //delete 版本 52 static void operator delete(void* ptr, size_t size) throw(); 53 54 //测试五、测试数组 55 static void *operator new[](size_t size); 56 static void operator delete[](void *ptr); 57 58 private: 59 int size; 60 char type; 61 };
那么问题来了,Apple 类和Fruit谁先构造、又谁先析构呢?进而思考,基类和子类谁更大一些?
众所周知,子类拥有父类的一切信息,而且子类有些信息更具体,比如鸟都有翅膀,这是共性。但是比如啄木鸟的嘴特别长,这就是特性。自然界是共性与特性的统一。
不过从哲学的角度来看,如“人是社会关系的总和”,讲的也是这个道理。
扯得有点远了,看图!所以构造时先构造内部,然后构造外部,析构时正好相反!
可以充分证明这个观点,还有问题的话,拷贝我上篇blog代码,可以有更详细的分析,这里就不展开讲了。毕竟只是开场白!
一、new和delete重载的实现及分析
1、重载时,一个类为空怎么处理?
一个类中,如果什么数据都没有!打印结果却是1
class Empty { }; int main(int argc, char** argv) { std::cout << sizeof(Empty) << std::endl; return 0; }
所以我们为类进行new 重载时应该也要考虑到这一点。至于为什么是1,不是0,也而不是其他的数据。我没弄清楚。但根据调试结果来分析,
我们在重载应该考虑到这一点。
首先应该判断下size是否为0。有指针时也要判断指针是否为空。
inline void* Apple::operator new(size_t size) { if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
2、operator new() 和 operator delete() 会自动转换为static 成员
由叶卡同学的blog中记录的 C++ Primer 557所示,成员operator new() 和 operator delete()会自动成为static成员。
因此,它们没有this指针,而且也不会修改物件内容,仅仅作为开辟空间、和清楚空间的作用!
3、operator new() 的三种形式:
throwing (1) void* operator new (std::size_t size) throw (std::bad_alloc); nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); placement (3) void* operator new (std::size_t size, void* ptr) throw();
第1、2种的区别 是有无抛出异常,其中有抛出异常的还可以进一步抛出信息,下面将会分析。
第3种 placement new,它也是对operator new的一个重载,定义于<new>中,它多接收一个ptr参数,但它只是简单地返回ptr。这里暂时没有详细分析,请同学自行查阅资料。(我上面的推荐资料里就有)
/* 测试二、抛出异常的版本 */ inline void* Apple::operator new(size_t size) { if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
运行图如下:
从上图分析得出,Fruit的Size为32,Apple 的Size为40。与上述相对应。
/* 测试三、没有抛出异常的版本 */ inline void* Apple:: operator new(size_t size, const std::nothrow_t& nothrow_value) { //即使是空类,大小也为1 if (size == 0) { return malloc(1); } else std::cout << "call Apple::operator new nothrow" << std::endl; return malloc(size); }
这个版本是没有返回异常信息的版本
如图所示,New的过程中那些打印信息并没有显示。
new 这类信息往往会用在调试代码阶段。能比较方便的显示出行数及文件信息。
* 测试四、抛出异常,并带有调试信息的版本 此版本使用时,会对以上两个版本发生冲突,需要注释掉另外两个函数,及使用 */ inline void* Apple::operator new(size_t size, const char* file, int line) { //即使是空类,大小也为1 if (size == 0) { return malloc(1); } void *ptr = malloc(size); if (ptr) { std::cout << "call A::operator new on file:" << file << " line:" << line << std::endl; cout << "Apple::size = " << size << " Apple::Address = " << ptr << endl; return (Apple*)ptr; } else { throw bad_alloc(); } }
在测试头部也要添加信息
//测试四、打开注释 //#define new new(__FILE__, __LINE__)
如图所示,显示了文件、及行数信息,方便调试。
4、何时重载类中、全局的 operator new()
/* 测试一、栈空间,使用自带的new 和全局new */ Apple ptrApple; Fruit *ptr = new Fruit(); delete ptr; Apple* ptr1 = new Apple();//Apple 是临时变量,所占空间是以new动态分配而得,并由p指向,占用空间为堆 delete ptr1;
这里有两种方法使用Apple 类,第一种为栈调用的方法,第二种为堆调用的方法(自己malloc)。这两种方法调用new 和delete的位置不同。
如图所示, 这里实际上有几个步骤:
1、分配内存.
2、指针类型转换
3、调用构造函数
分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),
通过以上结果对比,作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。
既先查找类中的operator new()和 operator delete(),然后再执行全局operator new()和 operator delete()。
5、多维数组的重载
/* 测试五、类中重载new[] 和 delete[] */ inline void* Apple::operator new[](size_t size) { //即使是空类,大小也为1 if (size == 0) { return malloc(1); } cout << "This is Apple New[]! Now allocating space :" << size << "Byte!" << endl; return malloc(size); } inline void Apple::operator delete[](void *ptr) { if (ptr) { cout << "This is Apple Delete[], Now free space!" << endl; free(ptr); } else { ptr = NULL; } }
Apple *ptr3 = new Apple[3]; cout << "ptr3[0] addr: " << ptr3 << endl; cout << "ptr3[1] addr: " << ptr3 + 1 << endl; cout << "ptr3[2] addr: " << ptr3 + 2 << endl; delete[] ptr3; ptr3 = NULL;
下面用图来解释下,(此图源于某blog内容,后面图保存了,却找不到来源,请作者勿怪,如有侵权,请联系我,谢谢)
delete的过程
烦请路过的朋友,批评指针。感谢网络的无私奉献者。 修改于 2016.08.15 17:28
内容修改中,8月15日晚11:30分前
上传最新版本