new和delete是c++中使用频率非常高的两个关键字,可以说C++内存操作的核心就在于这两个关键字,近几天阅读了相关的文章,发现自己对于这两个关键字的理解太过肤浅,因此做了一些个人总结。
首先要明确一点,new和delete所操作的内存全部是在堆区,这个区域的内存和栈区是不一样的,不会自动释放,因此一定要记得释放不使用的内存,否则会造成内存泄露。new操作有两种形式,一种是原始的new,一种是操作数组的new[],二者本质上没区别,只是操作的对象不同。对于系统内建的类型,new会直接分配定义好大小的内存区块,而对于用户自定义的类型,new会先调用构造函数,然后再分配内存区块。C++编译器会通过访问自定义类型的内部成员来计算该类型的大小,打个比方,class A里面有两个数据成员,一个是char,一个是int,那么该类对象所占的内存大小就是8+4=12字节。但是实际上,自定义类型的对象所占的内存大小是大于理论值的,原因就是C++编译器有一个cookie机制,这个机制能让自定义变量记录一些变量的全局信息,而内存大小就包括在这些信息里。这个cookie机制和web技术中的cookie其实有相似的地方,都是一种类似缓存的机制,有利于程序效率的提升。这种机制带来的一个好处就是用户在调用delete的时候再也不用指定内存的大小了,因为在调用delete的时候,编译器会自动读取cookie中分配内存的大小,从而直接释放内存,而不必像new似的,需要临时计算内存大小。而new[]的原理实际和new是一样的,只不过new[]是对数组中所有数据成员调用构造函数,然后分配内存,同理,delete[]也是对所有数据成员调用析构函数,然后释放内存。
对new和delete的重载具有很大意义,特别是在处理内存泄露方面。原始的new和delete虽然提供了一些处理内存异常的方法,但是在内存泄露方面基本是靠程序员自觉的,如果一个程序员忘记释放某部分内存,编译器不会提示错误。C++编译器之所以这么设计是有原因的,因为检测内存泄露所付出的代价往往是非常大的,编译器需要逐位检查数据是否置零,这对于小型数据来说也许不算什么,但如果对于集群数据来说,代价太大了,况且内存泄露的原因五花八门,这导致内存泄露在异常处理方面也会非常复杂,因此如果让new和delete支持内存泄露检测的话,它们的运行效率会下降非常多,而new和delete恰恰是c++中使用频率最高的两个操作,因此这类操作,效率肯定是放到第一位的,假如这类操作效率很低,对程序的负面影响是非常巨大的。但是这并不表示用户不可以定制自己的new和delete操作符,假如程序对内存检测的需求很高,那么用户完全可以重载这两个操作符。
其实new的重载远不是仅仅分配内存那么简单,在effective c++中,作者对new的重载做了比较深入的讲解,按照书中的观点,new除了分配内存,还有两个事情要做,一个是处理0字节的情况,一个是处理内存不够的情况,处理0字节的情况是把它当成1字节来处理,而处理内存不够的情况则需要借助于new_handler。new_handler是一个全局函数指针,指向一个处理内存不够的函数,这个指针通过<new>中的set_new_handler函数来设置指向的对象。如果用代码去实现的话,大概应该是这个样子:
void * operator new(size_t size)
{
if (size == 0)
{
size = 1;
}
new_handler globalhandler = std::set_new_handler(currenthandler);
std::cout<<"this is reloaded new"<<std::endl;
void *memory;
while(1)
{
try
{
memory = ::operator new(size);
}
catch(std::bad_alloc)
{
std::set_new_handler(globalhandler);
throw;
}
std::set_new_handler(globalhandler);
return memory;
}
}
其中currenthandler是用户自定义的内存出错处理函数,而globalhandler则是系统的全局出错处理函数。这个函数既处理了0字节情况,又处理了内存不够的情况,并且可以正常分配内存,可以说完成了new的基本功能,用户可以在这个基础上做任何扩展,都应该是没问题的。
而delete的重载则简单的多,由于不用指定内存大小,所以只需数据指针即可,但是,同样,delete需要处理空指针的情况,以保证delete的操作是安全的。那么实现的代码就是类似这种的:
void operator delete(void *rawmemory)
{
if (rawmemory == 0) return;
::operator delete(rawmemory);
return;
}
同样,用户可以在这个基础上做任何扩展。