GeekBand c++学习笔记——C++中的内存天下(2),new与delete拾遗

来源:互联网 发布:软件源码 编辑:程序博客网 时间:2024/06/05 14:16

引言

          这一次我们一方面接着之前对C++内存的探索,另一方面也是对第五周课程的总结。

定制new与delete

1、定制的必要性

          本周的课程主要讲述的是C++中可以通过重载operator new的方式来定制new和delete。最开始,心中尚有些疑惑,为何需要定制new与delete?通过查找资料发现,除了课堂上侯老师所说可以用于内存池,还有一些常见的理由:

1、用于检测错误。我们知道其实原本的new和delete是非常“危险的”,比如new失败了,我们在不知情的情况下delete了,又或者是new成功了,我们却多次delete了。这都是可能存在于我们的编程当中。我们先看代码:

#include <iostream>/* run this program using the console pauser or add your own getch, system("pause") or input loop */int main(int argc, char** argv) {int *p = new int;delete p;delete p;return 0;}

对于这样的代码,我们很容易看出是错误的,但是非常可惜,编译器是不会报错的,毫无疑问的,编译成功后运行肯定是失败的。如果在大型工程之中,固然有谁new谁delete的规定,我们也有可能因为代码跨度太大不小心多出了delete,最终运行失败,寻找错误就变得非常的麻烦。而如果使用定制的new与delete,我们就可以通过多分配一点空间,用于记录可能的错误信息,而进行检测了。

2、为了强化效能。

3、为了统计数据。

2、定制new和delete

       前面我们讨论了重新定制new和delete的必要性,现在我们开始正式定制一个自己的new和delete吧。虽然,侯老师在课堂上已经演示了一个,但是还是过于简单了,我们必须得弄一个实用一点的。首先,有必要说一下准则:
1、根据我们上一次的讨论,对于空类等原本理论为0byte的申请必须做出处理,即当遇到0byte的申请,我们需要为其分配1byte。
2、我们至少要对当内存不足时,new失败的时候做出相应的处理
3、对于operator delete,我们还需要对接收到的空指针做出处理,这样用以防止多次delete的错误出现
有了这几条准则,我们开始编程之旅吧:
#include <iostream>#include <cstdlib>using namespace std;void globalHandler(){cerr << "Unable to satisfy request for memory" << endl;abort();}void *operator new(size_t size) throw(bad_alloc){if(size == 0){cout << "Size is 1" << endl;return malloc(1);}while(true){void *pMem = malloc(sizeof(size));if(pMem){cout << "Size is " << size << endl;return pMem;}else{new_handler globalHandler = set_new_handler(0);set_new_handler(globalHandler);if(globalHandler){(*globalHandler)();}else{throw bad_alloc();}}}}void *operator new[](size_t size) throw(bad_alloc){if(size == 0){cout << "Size is 1" << endl;return malloc(1);}while(true){void *pMem = malloc(sizeof(size));if(pMem){cout << "Size is " << size << endl;return pMem;}else{new_handler globalHandler = set_new_handler(0);set_new_handler(globalHandler);if(globalHandler){(*globalHandler)();}else{throw bad_alloc();}}}}void operator delete(void *pMem){if(!pMem){cout << "Delete error: it has deleted!" << endl;return;}free(pMem);}void operator delete[](void *pMem){if(!pMem){cout << "Delete error: it has deleted!" << endl;return;}free(pMem);}class Empty{};
int main(){cout << "test1:" << endl;Empty *e = new Empty;cout << "test2:" << endl;int *p1 = new int;delete p1;p1 = NULL;delete p1;}
</pre><div>运行得到如下结果:</div><div><img src="http://img.blog.csdn.net/20160407231655287" alt="" /></div><div>可以看到,我们成功的解决了空类型和重复delete的问题。由于笔者虚拟机的内存达到了8G,为了验证内存申请不能得到满足,我们来了一种相当暴力的测试方式,循环申请:</div><pre class="cpp" name="code">int main(){cout << "test3:" << endl;        int i = 0;        while(true)        {                i++;                cout << "iterator num is " << i << endl;                int *p2 = new int[1 << 31];        }}

可以看到这种方式只申请不释放,就可以让系统慢慢的缺乏空间,直到不能满足new的需求,运行如下图:



我们可以看到,随着程序的运行,用过的内存越来越多,可用的内存越来越少(貌似笔者的内存有点大。。。这个迭代次数i也失去了效用,都超出范围好多次了。。。),最终还是没有申请完笔者的内存。但是,基本上可以肯定,在重载operator new时一旦申请失败必须要抛出异常或者终止操作。

new[]的大小

       事实上,对于这个专题,在课堂上老师是有所涉及的。类在堆中申请数组时,总会开辟比申请大小要大的空间,多余的空间用于一个计数器counter记录堆中数组的大小。比如在64位系统下申请一个五个Apple类对象的堆数组:Apple *a = new Apple[5],那么它的内存模型应该如下图所示:



为了证实这个问题,我们写一段代码:

#include <iostream>#include "Fruit.h"#include "Apple.h"#include "globalnew.h"using namespace std;/* run this program using the console pauser or add your own getch, system("pause") or input loop */   int main(int argc, char** argv) {cout << "test 4:" << endl;Apple *a = new Apple[5];const long long *counter = reinterpret_cast<const long long*>(reinterpret_cast<const char *>(a) - 8);cout << "counter addr: " << counter << "counter is " << *counter << endl;cout << "a[0] addr: " << a << endl;cout << "a[1] addr: " << a + 1 << endl;cout << "a[2] addr: " << a + 2 << endl;cout << "a[3] addr: " << a + 3 << endl;cout << "a[4] addr: " << a + 4 << endl;delete[] a;a = NULL;return 0;}



//Apple.h#ifndef _APPLE_H_#define _APPLE_H_#include <iostream>using namespace std;class Apple: public Fruit{int size;char type;public:Apple() : Fruit(), size(0), type('0'){cout << "This is Apple(" << size << ", " << type << ") constructor! this=" << this << endl;}Apple(const Fruit& fruit, const int& size, const char& type) : Fruit(fruit), size(size), type(type){cout << "This is Apple(" << size << ", " << type << ") constructor-1! this=" << this << endl;}Apple(const int& no, const double& weight = 0.0, const char& key = 0, const int& size = 0, const char& type = 0) : Fruit(no, weight, key), size(size), type(type){cout << "This is Apple(" << size << ", " << type << ") constructor-2! this=" << this << endl;}void save() {   }virtual void process(){ cout << "Call Apple process" << endl;}//测试用 ~Apple(){cout << "This is Apple(" << size << ", " << type << ") destructor! this=" << this << endl;}};#endif

//Fruit.h#ifndef _FRUIT_H_#define _FRUIT_H_#include <iostream>using namespace std;class Fruit{int no;double weight;char key;public:Fruit() : no(0), weight(0), key('0'){cout << "This is Fruit(" << no << ", " << weight << ", " << key << ") constructor! this=" << this << endl; }   Fruit(const int& no, const double& weight, const char& key) : no(no), weight(weight), key(key){cout << "This is Fruit(" << no << ", " << weight << ", " << key << ") constructor! this=" << this << endl;}//测试用    Fruit(const Fruit& other){   no = other.no;   weight = other.weight;   key = other.key;   cout << "This is Fruit copy constructor! this=" << this << endl;   }void print() {   }virtual void process(){  cout << "Call Fruit process" << endl; }//测试用 virtual ~Fruit(){cout << "This is Fruit(" << no << ", " << weight << ", " << key << ") destructor! this=" << this << endl;}};#endif


//globalnew.h#ifndef _GLOBALNEW_H_#define _GLOBALNEW_H_#include <cstdlib>inline void *myAlloc(size_t size){    return malloc(size);}inline void myFree(void *ptr){    free(ptr);    }inline void *operator new(size_t size){    cout << "This is global New(), Now allocating space :" << size << "Byte!"<< endl;    return myAlloc(size);}inline void operator delete(void *pdead){    if(pdead)    {        cout << "This is global Delete(), Now free space!"<< endl;        myFree(pdead);    }    else    {        pdead = NULL;    }}inline void *operator new[](size_t size){    cout << "This is global New[]! Now allocating space :" << size << "Byte!"<< endl;    return myAlloc(size);}inline void operator delete[](void *pdead){    if(pdead)    {        cout << "This is global Delete[], Now free space!"<< endl;        myFree(pdead);    }    else    {        pdead = NULL;    }}#endif
运行结果如下图:



可以看到,和内存图完全一致,也就是说,对于类的申请堆数组,的的确确的有一个计数器包含其中。

总结

       事实上,我们可以发现,重载operator new和operator delete是为了把原本的new和delete变得更为“智能”,犹如智能指针等概念,都是对原本概念进行一个再构造,最终方便我们编程人员的使用。通过几期关于C++内存总结,总算把一部分东西整理清楚了,后续将进行零散的拾遗啦





0 0