new/delete究竟做了些什么?
在理解这个问题之前,我们先看一下下面的这段程序,有这么一个程序段:
class A
{
public:
A() { cout<<"A is here!"<<endl; }
~A(){ cout<<"A is dead!"<<endl; }
private:
int i;
};
A* pA=new A;
delete pA;
在这个简单的程序段里面,new/delete究竟做了些什么?
实际上,这段程序里面隐含调用了一些我们没有看到的东西,那就是:
static void* operator new(size_t sz);
static void operator delete(void* p);
值得注意的是,这两个函数都是static的,所以如果我们重载了这2个函数(我们要么不重载,要重载就要2个一起行动),也应该声明为static的,如果我们没有声明,系统也会为我们自动加上。另外,这是两个内存分配原语,要么成功,要么没有分配任何内存。
size_t是什么东西呢?我在第一次看到这个动动的时候也是十分的困惑,毕竟以前没有见过。size_t在<cstddef>中定义,是一种无符号整数类型(不一定是int),用来保存对象的大小,这一用法是从C语言中借用过来的,现在你应该明白了吧(我学习的时候可是郁闷了好几天,没有人可以问,因为不知道有个csdn:)
new A;实际上做了2件事:调用opeator new,在自由存储区分配一个sizeof(A)大小的内存空间;然后调用构造函数A(),在这块内存空间上类砖砌瓦,建造起我们的对象。同样对于delete,则做了相反的两件事:调用析构函数~A(),销毁对象,调用operator delete,释放内存。不过需要注意的是,new分配一块内存的时候,并没有对这块内存空间做清零等任何动作,只是拿了过来,这块内存上放的仍然是原来的数据(垃圾数据),delete的时候,也只是释放这块内存,归还给操作系统,上面的数据还在上面,所以delete pA之后,pA的值没变,他指向的那块内存的值也没有变,不过似乎有什么问题,我们看一下下面的这个程序段:
int *p=new int(50000);
cout<<*p<<" "<<p<<endl;
delete p;
cout<<*p<<" "<<p<<endl;
我们可以清楚地看到,指针p存放的数据仍然是原来的地址,但是*p的内容却发生了变化,在我的机器上(win2000, VC6)始终是-572662307,不清楚这是为什么,难道系统做了什么手脚?还望高手指教。
在这里我们可以看到,new的工作实际上就是保证相互分离的存储分配和初始化工作能够很好的在一起工作,不过这里可能让初学者迷惑的是,我们定义了一个带有参数的new,但是我们用的时候却没有显式的去调用,而是让系统“神秘”的去提供这个参数。是的,这样做毫无疑问增加了复杂性,但是让基类获取了为一集派生类提供分配和释放服务的能力。
new/delete有什么好处和坏处?
从C程序员转换过来的C++程序员总是有个困惑:new/delete到底究竟和C语言里面的malloc/free比起来有什么优势?或者是一样的?
其实,就算我不说,你也应该很清楚了,new/delete当然比malloc/free要好,要不然,为什么还引进这个东东呢?其实通过上面的分析,我们看到了new/delete实际上做了很多malloc/free没有做的事情:malloc/free只是对内存进行分配和释放;new/delete还负责完成了创建和销毁对象的任务。
另外,new的安全性要高一些,因为他返回的就是一个所创建的对象的指针,对于malloc来说返回的则是void*,还要进行强制类型转换,显然这是一个危险的漏洞。
最后,我们可以对new/delete重载,使内存分配按照我们的意愿进行,这样更具有灵活性,malloc则不行。
不过,new/delete也并不是十分完美,大概最大的缺点就是效率低(针对的是缺省的分配器),原因不只是因为在自由存储区上分配(和栈上对比),具体的原因目前不是很清楚,不过在MCD上说了2个可能的原因:
1、new只是对于堆分配器(malloc/realloc/free)的一个浅层包装,没有针对小型的内存分配做优化。
2、缺省分配器具有通用性,它管理的是一块内存池,这样的管理往往需要消耗一些额外空间。
各种各样的new
一般来说,new有很多种形式,不过真的归纳起来,也就是2种:
1、最常用的形式:
void *operator new(std::size_t sz) throw(std::bad_alloc); (普通的)
void *operator new[](std::size_t sz) throw(std::bad_alloc); (数组的)
void *operator new(std::size_t sz);
void *operator new[](std::size_t sz)
这一种大家用得最为频繁,我就不举例子了。
2、放置new形式:
void *operator new(std::size_t count, void *ptr) throw(); (普通的)
void *operator new[](std::size_t count, void *ptr) throw(); (数组的)
要使用这种方式,必须包含头文件<new>。这个机制引入的初始目的是为了解决2个相关的问题:
1、把一个对象放在某个特定位置;
2、在某个特定分配区里面分配对象;
但是引入之后,发现这种机制远超出了简单的存储分配机制,我们可以给特定的存储位置关联任意的逻辑性值,这样一来,new就有了一种通用资源管理器的作用。同时第二个参数,也被扩展成了任意的可以识别的类型,并且配备了相应的nothrow版本:
void* operator new(std::size_t, const std::nothrow_t&) throw();
void* operator new[](std::size_t, const std::nothrow_t&) throw();
new能够返回NULL么?
我们经常看到有很多初学者喜欢写如下代码:
A* p=new A();
if(p==NUL) ....
写下这段代码的可能是受到了一些书上错误的影响,因为new A()从来就不可能返回NULL,如果在这个过程中用完了内存,那么他就会抛出bad_alloc异常,绝对不会返回NULL,如果你想让他返回null,应该用new(nothrow) A(),而不是new A()。不过从异常的观点来看,这实际上是一种倒退,我们应该尽量回避。