Chinaunix首页 | 论坛 | 博客
  • 博客访问: 379500
  • 博文数量: 38
  • 博客积分: 256
  • 博客等级: 入伍新兵
  • 技术积分: 846
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-14 23:21
文章分类

全部博文(38)

文章存档

2015年(1)

2014年(1)

2013年(28)

2012年(8)

我的朋友

分类: C/C++

2012-12-29 21:10:16

最近在网上看到好多篇博文,解释new/delete的背后故事,看完之后,总感觉,这些文章的理解有点偏颇,而且并没有真正懂得new和delete。基于这样的原因,我想写一篇博文,期望可以用通俗的方法解释它们。

1. 概念

new/delete操作分为两种:普通的new/delete和placement new/delete。

a. 普通new/delete:只接受一个参数的new/delete。当然这个参数对于new就是size,对于delete就是object pointer;

b. placement new/delete: 接收除了默认参数之外的参数。比如接收一个已分配好的内存,接收一个memory pool pointer等等。

(placement new/delete 是从一个概念衍生来的,那就是在一个内存块上执行constructor的new,而对应的delete被称为placement delete。后来这个概念被用于所有需要两个以上参数的new和delete)

2. 行为

a. new:new的行为包括两个部分,分配内存和在这块内存上构建一个对象。

b. delete: delete的行为也包括两个部分,析构一个对象,释放对应的内存。

有一件事情,我们都很清楚,上述的new的两部分和delete的两部分,都是我们可以定制的。我们可以重载operator new和operator delete来控制内存分配的部分,我们定义的类的constructors就是其构造的部分(对delete, 同理)。

但是另外一件事情,也许有很多人不是太清楚,那就是谁把这new的两个部分和delete的两个部分连在一起的呢?我们并没有明确的代码来连接这两部分啊。答案就是“编译器”,我们的好朋友。他会在我们调用new之后,帮我们准备好调用对象的构造函数,在我们调用delete之后,帮我们调用析构函数。

怎么?你不信?自己去反编译一下,你就全明白了。

 

3. 形式

这里主要讲的是new的形式,因为new是一个演化操作,在C中,它是一个函数,在C++中,它是一个操作符。按照C规范,它在分配内存失败是返回NULL,但是按照C++规范,它在分配内存失败时,返回一个不定值,但是会thow一个“bad_alloc" 异常。

这样问题就来了,异常在C++中并不是一个被大家都接受的东西(或者说特性)。即使在C++编写的软件中,就有明确的条款说,我们不使用C++的异常。对不对呢?为什么需要这个条款?一个主要原因是C++异常难以控制(如果我们追究过它的实现的话,其实它就是用goto语句实现的),另外一个是很多C++程序员都是从C转过来的,他们还不习惯使用异常来处理错误,特别是new失败的错误。

所以在C++中,我们就可以看到如下三种形式的new:

void* operator new(std:: size_t) throw (std:: bad_alloc);      //normal new

void* operator new(std:: size_t, void*) throw();    // placement new

void* operator new(std:: size_t, const std::nothrow_t&) throw();  // no throw new

第一种形式的new就是C++通常用的,它是在内存分配失败时抛出一个bad_alloc异常(并且返回值不定),第三种形式就是为了兼容C程序而提供的无异常的new,它会在内存分配失败时返回NULL。

这里还有一点很重要,就是在任何作用域中(比如class)定义其它形式的new,标准形式的new将被遮掩(这是符合我们常识的,比如在子类中定义一个父类的同名函数但是不同的参数列表,即使父类中的函数定义的是虚函数,它也会被遮掩)。

4. 重载

我们说过operator new 主要是用来负责内存分配的事宜的,是编译器把operator new和构造函数连在一起的。所以我们可以通过重载operator new来做很多我们想在内存分配阶段做的事情。比如多分配一些内存放在对象内存的前后用来保护对象内存,已经内存overwrite等常见的错误,已经加入一些统计信息。

这里有一个很重要的一点是,当我们重载new的时候,我会发现在Visual C++中,我们可以返回NULL (对normal new),但是在G++中,它总是报错,这就是C++编译器的实现,各个厂商会根据自己的理解来实现规范中没有明确规定的那部分。

也就是说Visual C++ 中,我们重载normal new时,我们可以返回一个NULL来跳过constructor阶段,但在G++中我们不可以。不是真的不可以,而是默认不可以,我们只需要加上一个编译参数“-fcheck-new”就可以了。

重载这部分,还有很多可以说的,请等待第二部分。

阅读(1595) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~