不同于C语言中的malloc/free是库函数,C++语言中的new/delete是运算符,而不是库函数。

new/delete执行流程

  我们经常会接触到的是new/delete operator(就是new/delete运算符)。其中new operator背后会调用operator new和placement new函数,而delete operator背后会调用operator delete函数。

  对于new operator而言,它的执行流程为:

  1)通过operator new函数申请内存
  2)使用placement new函数调用构造函数(简单类型如int,可忽略此步)
  3)返回内存指针

  而对于delete operator而言,它的执行流程为:

  1)调用析构函数(简单类型忽略此步)
  2)通过operator delete函数释放内存

new operator

  调用new,首先会先调用operator new函数申请内存。根据《深度探索C++对象模型》一书6.2节,一个不考虑异常处理的operator new函数经典实现如下:

 extern void*
operator new(size_t size)
{
if(size == )
size = ; void *last_alloc;
while(!(last_alloc = malloc(size)))
{
if(_new_handler) // _new_handler可自定义
(*_new_handler)();
else
return ;
}
return last_alloc;
}

  可以看出,operator new底层实际是调用了malloc来申请内存的。

  接下来就是调用placement new来对以申请到的内存进行初始化,也就是调用类的构造函数。实际上,placement new只是operator new的另一个重载版本,如下:

 void*
operator new(size_t, void *p)
{
return p;
}

  placement new就如此简单吗?实际并非如此,这只是它的一般操作,另一半操作无法由程序员产生出来。placement operator new的另一半操作正好是调用类的构造函数对已申请到的内存进行初始化。

delete operator

  调用delete,首先会调用类的析构函数,然后再调用operator delete函数释放类占用的内存。operator delete的简单实现如下:

 extern void
operator delete(void *ptr)
{
if(ptr)
free((Object *)ptr);
}

  也就是说,operator delete的底层实际调用的还是free库函数。

更多详细资料

  浅谈 C++ 中的 new/delete 和 new[]/delete[]

  C++中的new、operator new与placement new

  深入探究C++的new/delete操作符

  下文关于new失败后的正确处理摘自建议30:new内存失败后的正确处理

new失败后的正确处理

  通常,我们在使用new进行内存分配的时候,会采用以下的处理方式:

 char *pStr = new string[SIZE];
if(pStr == NULL)
{
... // Error processing
return false;
}  

  你能发现上述代码中存在的问题吗?这是一个隐蔽性极强的臭虫(Bug)。

  我们沿用了C时代的良好传统:使用 malloc 等分配内存的函数时,一定要检查其返回值是否为“空指针”,并以此作为检查分配内存操作是否成功的依据,这种Test-for-NULL代码形式是一种良好的编程习惯,也是编写可靠程序所必需的。可是,这种完美的处理形式必须有一个前提:若new失败,其返回值必须是NULL。只有这样才能保证上述看似“逻辑正确、风格良好”的代码可以正确运行。

  那么new失败后编译器到底是怎么处理的?在很久之前,即C++编译器的蛮荒时代,C++编译器保留了C编译器的处理方式:当operator new不能满足一个内存分配请求时,它返回一个NULL 指针。这曾经是对C的malloc函数的合理扩展。然而,随着技术的发展,标准的更新,编译器具有了更强大的功能,类也被设计得更漂亮,新时代的new在申请内存失败时具备了新的处理方式:抛出一个bad_alloc exception(异常)。所以,在新的标准里,上述Test-for-NULL处理方式不再被推荐和支持。

  如果再回头看看本建议开头的代码片段,其中的 if (pStr == 0 )从良好的代码风格突然一下变成了毫无意义。在C++里,如果 new 分配内存失败,默认是抛出异常。所以,如果分配成功,pStr == 0就绝对不会成立;而如果分配失败了,也不会执行if ( pStr == 0 ),因为分配失败时,new 就会抛出异常并跳过后面的代码。

  为了更加明确地理解其中的玄机,首先看看相关声明:

 namespace   std
{
class bad_alloc
{
// ...
};
} // new and delete
void *operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void *) throw(); // array new and delete
void *operator new[](std::size_t) throw(std::bad_alloc);
void operator delete[](void *) throw(); // placement new and delete
void *operator new(std::size_t, void *) throw();
void operator delete(void *, void *) throw(); // placement array new and delete
void *operator new[](std::size_t, void *) throw();
void operator delete[](void *, void *) throw();

  在以上的new操作族中,只有负责内存申请的operator new才会抛出异常std::bad_alloc。如果出现了这个异常,那就意味着内存耗尽,或者有其他原因导致内存分配失败。所以,按照C++标准,如果想检查 new 是否成功,则应该捕捉异常:

 try
{
  int* pStr = new string[SIZE];
... // processing codes
}
catch ( const bad_alloc& e )
{
return -;
}

  但是市面上还存在着一些古老编译器的踪迹,这些编译器并不支持这个标准。同时,在这个标准制定之前已经存在的很多代码,如果因为标准的改变而变得漏洞百出,肯定会引起很多人抗议。C++标准化委员会并不想遗弃这些 Test-for-NULL的代码,所以他们提供了operator new 的另一种可选形式— nothrow ,用以提供传统的Failure-yields-NULL行为。

  其实现原理如下所示:

 void * operator new(size_t cb, const std::nothrow_t&) throw()
{
char *p;
   try
   {
  p = new char[cb];
   }
   catch (std::bad_alloc& e)
   {
  p = ;
   }
return p;
}

  文件中也声明了nothrow new的重载版本,其声明方式如下所示:

 namespace   std
{
struct nothrow_t
{
// ...
};
extern const nothrow_t nothrow;
} // new and delete
void *operator new(std::size_t, std::nothrow_t const &) throw();
void operator delete(void *, std::nothrow_t const &) throw(); // array new and delete
void *operator new[](std::size_t, std::nothrow_t const &) throw();
void operator delete[](void *, std::nothrow_t const &) throw();

  如果采用不抛出异常的new形式,本建议开头的代码片段就应该改写为以下形式:

 int* pStr = new(std::nothrow) string[SIZE];
if(pStr==NULL)
{
... // 错误处理代码
}

  根据建议29可知,编译器在表达式 new (std::nothrow) ClassName中一共完成了两项任务。首先,operator new 的 nothrow 版本被调用来为一个ClassName object分配对象内存。假如这个分配失败,operator new返回null指针;假如内存分配成功,ClassName 的构造函数则被调用,而在此刻,对象的构造函数就能做任何它想做的事了。如果此时它也需要new 一些内存,但是没有使用 nothrow new形式,那么,虽然在"new (std::nothrow) ClassName" 中调用的operator new 不会抛出异常,但其构造函数却无意中办了件错事。假如它真的这样做了,exception就会像被普通的operator new抛出的异常一样在系统里传播。所以使用nothrow new只能保证operator new不会抛出异常,无法保证"new (std::nothrow) ClassName"这样的表达式不会抛出exception。所以,慎用nothrow new。

  最后还需要说明一个比较特殊但是确实存在的问题:在Visual C++ 6.0 中目前operator new、operator new(std::nothrow) 和 STL 之间不兼容、不匹配,而且不能完全被修复。如果在非MFC项目中使用Visual C++6.0中的STL,其即装即用的行为可能导致STL在内存不足的情况下让应用程序崩溃。对于基于MFC的项目,STL是否能够幸免于难,完全取决于你使用的 STL 针对operator new的异常处理。这一点,在James Hebben的文章《不要让内存分配失败导致您的旧版 STL 应用程序崩溃》中进行了详细的介绍,如果你在使用古老的Visual C++ 6.0编译器,而且对这个问题充满兴趣,请Google之。

  请记住:

  当使用new申请一块内存失败时,抛出异常std::bad_alloc是C++标准中规定的标准行为,所以推荐使用try{ p = new int[SIZE]; } catch( std::bad_alloc ) { ... }的处理方式。但是在一些老旧的编译器中,却不支持该标准,它会返回NULL,此时具有C传统的Test_for_NULL代码形式便起了作用。所以,要针对不同的情形采取合理的处置方式。

C++中的new/delete的更多相关文章

  1. 数据库设计中的Soft Delete模式

    最近几天有点忙,所以我们今天来一篇短的,简单地介绍一下数据库设计中的一种模式——Soft Delete. 可以说,该模式毁誉参半,甚至有非常多的人认为该模式是一个Anti-Pattern.因此在本篇文 ...

  2. c++中new和delete的使用方法

    c++中new和delete的使用方法 new和delete运算符用于动态分配和撤销内存的运算符 new用法: 1.     开辟单变量地址空间 1)new int;  //开辟一个存放数组的存储空间 ...

  3. C++中的new/delete与operator new/operator delete

    new operator/delete operator就是new和delete操作符,而operator new/operator delete是函数. new operator(1)调用opera ...

  4. oracle中drop、delete和truncate的区别

    oracle中drop.delete和truncate的区别 oracle中可以使用drop.delete和truncate三个命令来删除数据库中的表,网上有许多文章和教程专门讲解了它们之间的异同,我 ...

  5. 数据库中drop、delete与truncate的区别

    数据库中drop.delete与truncate的区别 drop直接删掉表: truncate删除表中数据,再插入时自增长id又从1开始 :delete删除表中数据,可以加where字句. (1) D ...

  6. SQL server触发器中 update insert delete 分别给写个例子被。

    SQL server触发器中 update insert delete 分别给写个例子以及解释下例子的作用和意思被, 万分感谢!!!! 主要想知道下各个语句的书写规范. INSERT: 表1 (ID, ...

  7. java中File的delete()方法删除文件失败的原因

    java中File的delete()方法删除文件失败的原因 学习了:http://hujinfan.iteye.com/blog/1266387 的确是忘记关闭了: 引用原文膜拜一下: 一般来说 ja ...

  8. SQL语句的使用,SELECT - 从数据库表中获取数据 UPDATE - 更新数据库表中的数据 DELETE - 从数据库表中删除数据 INSERT INTO - 向数据库表中插入数据

    SQL DML 和 DDL 可以把 SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL). SQL (结构化查询语言)是用于执行查询的语法. 但是 SQL 语言也包含用于更新. ...

  9. 浅谈 C++ 中的 new/delete 和 new[]/delete[]

    在 C++ 中,你也许经常使用 new 和 delete 来动态申请和释放内存,但你可曾想过以下问题呢? new 和 delete 是函数吗? new [] 和 delete [] 又是什么?什么时候 ...

随机推荐

  1. Hive-RCFile文件存储格式

    在新建Hive表时,可以使用stored as rcfile来指定hive文件的存储方式为RCFile. 一.RCFile文件结构 下图是一个RCFile的文件结构形式. 从上图可以看出: 1)一张表 ...

  2. 二维码扫描&集合排序

    一.二维码扫描机制 二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的:在代码编制上巧妙地利用构 ...

  3. nfc开发

    很多Android设备已经支持NFC(近距离无线通讯技术)了.本文就以实例的方式,为大家介绍如何在Android系统中进行NFC开发. Android NFC开发环境 使用硬件:Google Nexu ...

  4. 电脑hash破解

    我一直在想,到底用什么样的方式才能较长时间地控制已经得到了权限的目标呢?留后门,种木马,甚至是Rootkit?留的Webshell痕迹太明显,哪怕是一句话的Webshell,都极容易被管理员清除.放了 ...

  5. 深入浅出Tabhost+简单入门Demo

    小伙伴们在手机上逛淘宝的时候,会发现在淘宝的下面有个按钮,分别是首页.微淘.社区.购物车和我的淘宝,点击不同的按钮会跳转到不同的页面,目前小编所接手的这个项目,也需要用到类似这样的功能,小编就发挥网络 ...

  6. JQuery实战--可以编辑的表格

    廊坊下雪了,15年的第二场雪,比14的来的稍晚一些,停靠在11教门前的自行车,成了廊坊师范学院最美丽的风景线.还记得以前学习css的时候,就曾经接触过如何编写设计一些表格和表单的样式,例如如何设计表格 ...

  7. Java-IO之PrintStream(打印输出流)

    PrintStream是打印输出流,继承于FilterOutputStream,PrintStream是用来装饰其他输出流,为其他输出流添加功能,方便他们打印出各种数据值表示形式.与其他输出流不同,P ...

  8. Android4.4.2KK竖屏强制更改为横屏的初步简略方案

    点击打开链接 解决方案: 当前是根据当前问题场景即竖屏强制更改为横屏的需求而做的改动,基本是hardcode定义的状态,总共修改有效代码行数5行,如果后续有其他需求或者需要更灵活的配置横屏和竖屏,可以 ...

  9. [Error]Can't install RMagick 2.13.4. You must have ImageMagick 6.4.9 or later.

    gem 安装ruby插件的时候 出现了一个错误 Installing rmagick 2.13.4 with native extensions Gem::Installer::ExtensionBu ...

  10. Mybatis执行CachingExecutor(六)

    前面几篇博客我们介绍了Excutor及抽象类BaseExecutor和实现类SimpleExecutor.BatchExecutor和ReuseExecutor: 博客列表: Mybatis执行Exe ...