c++ new delete
当创建一个c++对象时,会发生两件事:
(1)为对象分配内存。
(2)调用构造函数初始化这块内容。
这块内存可以位于3个区域:静态存储区、栈、堆。
operator new
当用new创建一个对象时,它将在堆里为对象分配内存(使用“operator new()”)并为这块内存调用构造函数。
MyType * fp = new MyType;
operator delete
与new结合使用。delete先调用析构函数,然后释放内存。
delete fp;
同一指针delete两次会产生未定义行为(程序可能崩溃),所以在delete某一指针之后最好将其置0,因为delete零值指针是安全的(其行为是什么都不做)。
当在栈中创建对象时,编译器知道其确切的类型、数量、所占内存的大小、生存期,这些信息将被准确的记录在目标代码中。当在堆中创建对象时(称为动态创建对象),需要在运行时动态搜索一块足够大的内存以满足要求,并将这块内存的大小和地址记录下来,防止它被再次占用,同时在释放内存时系统能够知道该释放多大的内存。所以在堆中创建对象和在栈中创建对象相比,系统所花费的内存管理的开销要大。
delete void* 可能会造成内存泄露
delete void*指针只会释放当前对象所占用的内存,不会调用其析构函数。下面的代码将会造成memory leak。
class Object { void *data; // Some storage const int size; const char id; public: Object(int sz, char c) : size(sz), id(c) { data = new char[size]; cout << "Constructing object " << id << ", size = " << size << endl; } ~Object() { cout << "Destructing object " << id << endl; delete [] data; } }; int main() { void *obj = new Object(40, 'a'); delete obj; }
用于数组的new []和delete []
MyType *fp = new MyType[100];
这里要求MyType拥有一个工作良好的默认构造函数(如果程序员没有编写默认构造函数,编译器会自动生成一个)。
delete [] fp;
空方括号的作用是告诉编译器,应该将创建数组时存放在某处的对象数量取回,并为数组的所有对象调用析构函数。
new-handler
当operator new()分配内存失败时,一个称为new-handler的特殊函数将会被调用。new-handler的默认动作是产生一个异常,我们可以自定义一个新的new-handler函数。
#include <iostream> #include <cstdlib> #include <new> using namespace std; int count = 0; void out_of_memory() { cerr << "memory exhausted after " << count << " allocations!" << endl; exit(1); } int main() { set_new_handler(out_of_memory); while(1) { count++; new int[1000]; // Exhausts memory } }
new-handler函数不含参数且返回值为void。可以编写更为复杂的new-handler,甚至可以回收内存(把new-handler实现为一个garbage collector)。
new-handler会试着调用operator new(),如果已经重载了operator new(),则new-handler将不会按默认调用。
重载new和delete
new表达式先使用operator new()分配内存,然后调用构造函数;delete表达式先调用析构函数,然后调用operator delete()释放内存。
可以重载operator new()和operator delete()以改变原有的内存分配和释放方法。重载的目的通常是为了加快内存分配速度或者减少内存碎片等等。
当重载operator new()时,同时也必须决定当内存分配失败时去做什么。返回0?、写一个调用new-handler的循环?、重新尝试分配?、(典型做法)产生一个bad_alloc异常?。
可以重载全局new和delete,也可以重载属于特定类的new和delete。
重载全局new和delete
重载全局new和delete是很极端的做法,这将导致默认版本完全不能被访问。
重载的operator new()有一个size_t参数,代表要分配的内存的字节大小。返回类型为void *,而不是指向特定类型的指针。因为operator new()仅仅是分配内存,并不能完成对象建立。对象建立需要调用构造函数,这是编译器确保完成的动作,不在我们的控制范围之内。
重载的operator delete()有一个void *参数,指向要释放的内存。返回类型为void。
#include <cstdio> #include <cstdlib> using namespace std; void * operator new(size_t sz) { printf("operator new: %d Bytes\n", sz); void *m = malloc(sz); if(!m) puts("out of memory"); return m; } void operator delete(void *m) { puts("operator delete"); free(m); } class S { int i[100]; public: S() { puts("S::S()"); } ~S() { puts("S::~S()"); } }; int main() { puts("creating & destroying an int"); int *p = new int(47); delete p; puts("\n"); puts("creating & destroying an s"); S *s = new S; delete s; puts("\n"); puts("creating & destroying S[3]"); S *sa = new S[3]; delete [] sa; puts("\n"); }
在重载的operator new()和operator delete()中使用的是printf()和puts()而不是iostream,因为当创建一个iostream对象(如全局cin、cout、cerr),会调用全局new分配内存,这样将造成死循环。
通过示例程序的输出结果可以看出,无论是创建内置数据类型对象,还是自定义类类型对象(及数组),都使用了重载的全局new和delete。
对于一个类重载new和delete
当为一个类重载new和delete时,实际上是创建其static成员函数operator new()和operator delete()(即使没有显示写出static关键字)。当编译器看到使用new创建其类型对象时,会选择使用其成员版本的operator new()而不是全局new。
#include <cstddef> // size_t #include <fstream> #include <iostream> #include <new> using namespace std; ofstream out("Framis.out"); class Framis { enum { sz = 10 }; char c[sz]; // To take up space, not used static unsigned char pool[]; static bool alloc_map[]; public: enum { psize = 10 }; // frami allowed Framis() { out << "Framis()\n"; } ~Framis() { out << "~Framis() ... "; } void * operator new(size_t) throw(bad_alloc); void operator delete(void *); }; unsigned char Framis::pool[psize * sizeof(Framis)]; bool Framis::alloc_map[psize] = {false}; // Size is ignored -- assume a Framis object void * Framis::operator new(size_t) throw(bad_alloc) { for(int i = 0; i < psize; i++) if(!alloc_map[i]) { out << "using block " << i << " ... "; alloc_map[i] = true; // Mark it used return pool + (i * sizeof(Framis)); } out << "out of memory" << endl; throw bad_alloc(); } void Framis::operator delete(void *m) { if(!m) return; // Check for null pointer // Assume it was created in the pool // Calculate which block number it is: unsigned long block = (unsigned long)m - (unsigned long)pool; block /= sizeof(Framis); out << "freeing block " << block << endl; // Mark it free: alloc_map[block] = false; } int main() { Framis* f[Framis::psize]; try { for(int i = 0; i < Framis::psize; i++) f[i] = new Framis; new Framis; // Out of memory } catch(bad_alloc) { cerr << "Out of memory!" << endl; } delete f[5]; f[5] = 0; // Use released memory(delete f[5]): Framis *x = new Framis; delete x; for(int j = 0; j < Framis::psize; j++) delete f[j]; // Delete f[10] OK }
Framis.out输出结果如下:
using block 0 ... Framis() using block 1 ... Framis() using block 2 ... Framis() using block 3 ... Framis() using block 4 ... Framis() using block 5 ... Framis() using block 6 ... Framis() using block 7 ... Framis() using block 8 ... Framis() using block 9 ... Framis() out of memory ~Framis() ... freeing block 5 using block 5 ... Framis() ~Framis() ... freeing block 5 ~Framis() ... freeing block 0 ~Framis() ... freeing block 1 ~Framis() ... freeing block 2 ~Framis() ... freeing block 3 ~Framis() ... freeing block 4 ~Framis() ... freeing block 6 ~Framis() ... freeing block 7 ~Framis() ... freeing block 8 ~Framis() ... freeing block 9
注意:class Framis重载的new和delete,其派生类是不能使用的。也就是派生类不会继承基类重载的new和delete。
为类重载 operator new []和operator delete []
如果只为类重载了operator new()和operator delete(),当创建这个类的对象数组时,还是会使用全局operator new ()。可以为类重载operator new []和operator delete []以控制其对象数组的内存分配。
#include <new> // size_t definition #include <fstream> using namespace std; ofstream trace("ArrayOperatorNew.out"); class Widget { enum { sz = 10 }; int i[sz]; public: Widget() { trace << "*"; } ~Widget() { trace << "~"; } void * operator new(size_t sz) { trace << "Widget::new: " << sz << " bytes" << endl; return ::new char[sz]; } void operator delete(void *p) { trace << "Widget::delete" << endl; ::delete [] p; } void * operator new[](size_t sz) { trace << "Widget::new[]: " << sz << " bytes" << endl; return ::new char[sz]; } void operator delete[](void *p) { trace << "Widget::delete []" << endl; ::delete [] p; } }; int main() { trace << "new Widget" << endl; Widget *w = new Widget; trace << "\ndelete Widget" << endl; delete w; trace << "\nnew Widget[25]" << endl; Widget *wa = new Widget[25]; trace << "\ndelete [] Widget" << endl; delete [] wa; }
跟踪文件的输出信息:
new Widget Widget::new: 40 bytes * delete Widget ~Widget::delete new Widget[25] Widget::new[]: 1004 bytes ************************* delete [] Widget ~~~~~~~~~~~~~~~~~~~~~~~~~Widget::delete[]
当创建单个Widget对象时,首先调用类重载的operator new()函数分配40个字节大小的内存。然后调用构造函数(输出*)。删除单个对象时,先调用析构函数(输出~),再调用类重载的operator delete()。
当创建Widget对象数组时,首先调用类重载的operator new[]。需要分配的内存额外多出4个字节,这4个字节是系统用来存放数组信息的,比如数组中对象的数量。然后调用25次Widget构造函数。当删除对象数组时(delete [] Widget),方括号告诉编译器要删除的是一个对象数组。编译器会先获取那4个字节所存储的数组信息,得到对象数量并多次(25)调用析构函数,然后调用类重载的operator delete[]。
构造函数调用
如果使用new操作符时内存分配失败,则编译器不会调用构造函数。
#include <iostream> #include <new> // bad_alloc definition using namespace std; class NoMemory { public: NoMemory() { cout << "NoMemory::NoMemory()" << endl; } void * operator new(size_t sz) throw(bad_alloc) { cout << "NoMemory::operator new" << endl; throw bad_alloc(); // "Out of memory" } }; int main() { NoMemory *nm = 0; try { nm = new NoMemory; } catch(bad_alloc) { cerr << "Out of memory exception" << endl; } cout << "nm = " << nm << endl; }
程序输出:
placement new和placement delete
placement new(定位new)在已分配的原始内存中初始化一个对象。它与new的其他版本的不同之处在于,它不分配内存。相反,它接受指向已分配但未构造的内存的指针,并在该内存中初始化一个对象。placement new表达式能够在特定的、预分配的内存地址上构造一个对象。
#include <cstddef> // size_t #include <iostream> using namespace std; class X { int i; public: X(int ii = 0) : i(ii) { cout << "this = " << this << endl; } ~X() { cout << "X::~X(): " << this << endl;} void * operator new(size_t, void *loc) { return loc; } }; int main() { int l[10]; cout << "l = " << l << endl; X *xp = new(l) X(47); // X at location l xp->X::~X(); // Explicit destructor call // ONLY use with placement! }
输出结果:
【学习资料】 《Thinking in c++》