高度注意并警惕 placement new [] 的陷阱

原创 2011年01月24日 14:22:00

前几天在检一个服务器容器的时候,发现一个严重的问题,问题大概如下:

 

假设我们有这样一个类:

 

和假设如下的代码:

 

按照MSDN上面说明,我们可以认为p和pk所处的地址是相同的,仍而,
如果我们把类中的tk成员注掉,执行上面两句结果是p和pk地址相等。
但我们把类中的tk成员打开,再执行,p和pk地址却不相等。
经调试发现,中间差了4字节,里面存放的刚好就是数组的数量,即4。

 

我问了两个群的编程兄弟,他们都很热情,我很感谢,不过问题说得不是很清楚,但我更喜欢查根问底。
虽然我知道怎么解决这个BUG,让程序正常起来,但我还是硬着查清楚原因。

 

在解释原因之前我们先将结构改一下(去掉tk,增加析构函数):

 

好了,这个时候再来以有析构和没有析构函数的方法测试:
当有析构的时候p和pk地址不同,当没有析构的时候p和pk相同。

 

是什么原因造成这一现象的呢?
有没有析构又怎么会造成这一结果的呢?
类析构和std::vector< int > tk有什么联呢?
下面依次解释:

 

虽然FFntTexXX是一个类,但其成员没有一个成员有析构函数,所以编译器认为此类可以退化到结构。
(当类中有任意一个成员需要析构,如std::vector tk成员,编译器为自动为类FFntTexXX生成析构)
或许你又要问了,为什么生成析构与不生成析构和p、pk有什么关系呢?
确实是的,起初我也一直认识这个不存在任何关系的,但编过汇编发现,原因正是因为析构。

 

先看 ::new [] 函数代码(无析构):

 

再看 ::new [] 函数代码(有析构):

 

这里可能你也已经发现了,当有析构的时候new[]会要求多传入一个机器字长数据,当然目的就是为了保存数组的个数,
而没有析构的时候却不需要保存数组的个数。

 

接下来再看 constructor iterator 函数(无析构):

 

然后看 constructor iterator 函数(有析构):

 

初看这好像没有什么特别的,但细看才发现,两个数组构造器函数不一样,无析构的只有4个参数,而有析构的有5个参数。

“vector constructor iterator”和“eh vector constructor iterator”。
这时才恍然大悟,原因编译器做的事情不少。

 

总结:
因为没有析构函数的对象,在delete[]的时候,它可以直接从内存拿掉而不用调析构函数。
所以这里不用记录这个数组有多少个元素,也就不出现偏4个字节。
当有析构函数的时候,编译器为了记住有多少个对象,以便于在delete[]的时候要对其调用析构函数,
所以在数组首地址前面一个机器字长来保存数组长度,数组总尺寸增加一个机器的字长。
因此调用new[]的时候,有析构和无析构的时候,所需要的内存不一样。

 

解决方案:
使用 placement new[] 对给定地址进行数组构造的时候(placement new不存在此问题),
一定记住要多分配一个机器字长,用sizeof(int)即可得到所需字节数。
真正的数组地址应该取 placement new[] 返回的地址,而不是取 placement new(p) [] 中p的地址。

c++ placement new操作符的使用技巧

在c++中new和delete操作是我们操作内存最常用的一对操作符,在使用new时编译器会申请内存,然后调用类的构造函数来初始化对象,调用delete会销毁对象同时释放该对象占用的内存,并且我们可以重...
  • D_Guco
  • D_Guco
  • 2017年01月04日 23:18
  • 1101

注意: new operator和operator new 以及placement new的差异【转载】

1:前言:本文是在参考了几个博客之后的个人总结,最后贴上自己在理解时使用的测试代码,以及自己对原博文的理解,原博文的地址请留意文章后面的参考资料。对于本文的总结理解有疑问或错误的地方,欢迎留言讨论或指...
  • u013777351
  • u013777351
  • 2015年09月07日 21:23
  • 264

CSS及HTML 常见误区和注意事项(一)

CSS及HTML 常见误区和注意事项(一)为什么要设置HTML和body的高度
  • qq_25684647
  • qq_25684647
  • 2017年03月15日 21:21
  • 170

Placement new 存在的理由

Placement new 存在的理由 (1).用Placement new 解决buffer的问题 问 题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默...
  • cncnlg
  • cncnlg
  • 2015年03月18日 22:12
  • 220

Placement new的用法及用途

所谓placement new就是在用户指定的内存位置上构建新的对象,这个构建过程不需要额外分配内存,只需要调用对象的构造函数即可。 举例来说: class foo{}; foo* pfoo = ne...
  • duhaomin
  • duhaomin
  • 2014年01月27日 16:31
  • 913

placement new的用法及用途

本文转自:http://www.cppblog.com/kongque所谓placement new就是在用户指定的内存位置上构建新的对象,这个构建过程不需要额外分配内存,只需要调用对象的构造函数即可...
  • tennysonsky
  • tennysonsky
  • 2017年07月03日 01:49
  • 250

警惕自增的陷阱

public class Client { public static void main(String[] args) { int cou...
  • lexang1
  • lexang1
  • 2015年10月11日 22:11
  • 296

有关allocator配置器的一点小总结,自己实现allocator类

文章内容来自《STL源码剖析》中有关allocator配置器的一些小总结,还有内存分配和释放的两个细化,后面附上一个自己定义的allocator类。...
  • jmy99527
  • jmy99527
  • 2013年11月19日 09:17
  • 1220

Java NIO开发需要注意的陷阱(转)

转自 陷阱1:处理事件忘记移除key 在select返回值大于0的情况下,循环处理 Selector.selectedKeys集合,每处理一个必须从Set中移除 I...
  • martin_liang
  • martin_liang
  • 2014年11月17日 23:30
  • 810

C++中placement new操作符(经典)

placement new是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替(不像普通的operator new和operator delete能够被替换成用户自定义的版本...
  • zhaonin
  • zhaonin
  • 2015年08月30日 10:42
  • 154
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:高度注意并警惕 placement new [] 的陷阱
举报原因:
原因补充:

(最多只允许输入30个字)