在delete和delete[]背后,编译器的不同对待

先看下面程序:

复制代码
#include <iostream>
using namespace std;
#include <string>

int main()
{
    int *p=new int[5];
    //delete p;
    delete []p;
    p=NULL;

    //string *p=new string[5];
    //delete p;
    //delete []p;
    //p=NULL;

    return 0;
}
复制代码

对于int类型和string类型,delete p和delete []p编译器(vc6.0)会有不一样的结果,对于int类型delete p和delete []p均可以编译运行,而 对于string类型,delete p运行

后程序会崩溃。

下面是对这个问题网友的意见与我的总结:

(1)delete 与delete[]都会释放所有内存,它们的不同点仅在于,根据得到的类型信息是单个指针还是指向数组的指针,来决定调用析构函数的次数。而编译器对所要删除的那个指针到底是指向的单一对象还是对象数组的判断依据就是"[]"的有无。若有'[]'编译器得到的类型信息就是指向数组的指针,然后调用多次析构函数;若没有‘[]’编译器得到的类型信息就是单个指针,只调用一次析构函数。
其实delete操作本身就是做两件事:
<1> 针对此处内存调用析构函数
<2> 然后释放该处内存。
(详见《Effective C++》(2nd)item 5 或 (3rd)item 16)。

 

(2)vc6.0和vs05中,delete和delete[]语句都是调用operator delete(),在vs05中调试,在跟进operator delete()中,可看到有这样一句“_free_dbg( pUserData, pHead->nBlockUse );”而free要正确工作,有一个必要前提,即传给它的地址确实是当初申请的内存首地址,否则,会出现assert错误。即在重载delete操作符后,某种情况下发现delete操作的指针地址不同于new操作所获取的地址。从而出现assert错误,程序崩溃。

(3)在VC中,对于有显式析构函数的对象, 在分配数组时其前会有一个4字节前缀用来保存数组元素个数. 如果用delete来释放数组, 就会导致释放的内存地址与分配时的内存地址出现4字节偏差, 而导致灾难性的错误.


(4)那么什么情况下,二者操作的指针地址不匹配呢?
经过反复实验测试,发现只要对象类型定义有显式析构函数,那么这个4字节就肯定存在。也就是说,哪怕是我们自定义的一个类类型对象,只要我们没有为这个类定义析构函数,那么这个4字节问题就不会出现;那么该用delete[]的时候,用delete也不会报错;那么该用delete的时候,用delete[]也不会报错,那么...
本论题,由于内置类型没有析构函数,而string有析构函数,这样一种巧合,而把问题局限在了内置类型与自定义类型这样的分类范围上。所以我们这个问题准确的说,应该是显式析构函数的存在影响了delete与delete[]的处理。

 

(5)一句话:该用delete[]时莫省略,这是C++标准。



默认的构造函数和默认的析构函数是个什么样子的?

默认的构造函数里面实际就是分配了内存。
默认的析构函数里面实际就是释放了占用的内存,其他啥也没干了。
那么析构函数里面是怎么知道要释放多少内存的呢?就烦请大家去看看操作系统里的内存是怎么管理的,大致意思是:操作系统维护了一个内存分配表,指示内存的哪块用了,例如0x1000地址开始的100个字节是已经使用的内存,大概记录这种东西。释放内存的时候呢,必须一块一块的删除,例如0x1000开始的内存,不能从0x1001开始删除,必须从0x1000开始删除,因为free()或者delete函数都是给出的内存首地址,然后根据占用长度进行删除。就是这样的。举个例子,free(0x1000),首先要去操作系统的这个内存维护表里去找索引0x1000这个块,如果找到了,就说明给定的这个首地址是正确的,而且正使用着呢,于是就执行释放内存操作,实际就是把这一项从内存维护表里删除,至于操作系统做的其他的操作,大家去查查别的资料。再例如,free(0x1001),如果在内存维护表里面没有找到0x1001的已经使用的项,但存在某个内存使用块的内存范围里包含0x1001,例如(0x1000,100),这个范围就包含了0x1001,那么free操作就会出现运行时错误。如果出现的情况也不属于上面两种情况,free()实际上就什么也不做,也不会出现运行时错误,就返回了。
知道了上面这些,我们继续下面的讨论。
为什么带有析构函数的类,在创建对象数组时,会多出四个字节?为什么不带有析构函数的类,在创建对象数组时,就不需要这个四个字节呢?
因为带有析构函数的类的对象数组,在使用delete[] ptr时需要知道,到底需要执行多少次析构函数,然后再执行释放内存的操作。那么执行析构函数的次数,这个值就放到了申请空间的最最前面的四个字节,紧跟这个四个字节的才是实际的对象数组。但这个时候,编译器做了一下处理,因为这四个字节不属于任何对象,只是编译器知道就好了,因此返回对象数组的指针的时候,不是实际申请的内存空间,而是返回的跳过了开始的四个字节的地址。我在一个地方看到的编译器cookie的概念就包含这四个字节。
而没有析构函数的类的对象数组,它不需要知道要执行多少次析构函数,尽管!尽管课本上都有说有一个默认的析构函数。如果大家看过反汇编代码的话就知道了,这个默认析构函数里面什么都没有,甚至有些编译器直接处理成没有,只是逻辑上存在。因此C++编译器们在处理delete[]的时候,都选择了根本就不执行类的默认析构函数,而只需要执行释放内存的操作。因此它不需要这四个字节。
那么我们接着分析下面的问题就简单多了。
delete i_p; 没有析构函数,仅释放空间
delete []i_p; 没有析构函数,仅释放空间
大家读了上面的如何释放空间之后,就知道,这俩是一样的,但是也跟不同的编译器有关系,有些编译器就要求new[]的东西必须使用delete[]释放,比如VC++2010编译器的默认build选项就会报运行时错误。
string是带有析构函数的
delete s_p; //执行了一次s_p[0]对象的析构函数,析构函数中释放s_p[0]的空间,然后s_p数组的5个内存空间释放了。

delete[] s_p; //执行n次析构函数,每次执行析构函数就释放每个string对象的空间,最后s_p数组的5个内存空间释放了。


原帖如下:

(1)http://topic.csdn.net/u/20080826/16/0c479e3d-737f-45c7-995e-bd316f5fa166.html

(2)http://topic.csdn.net/u/20080221/20/7c7093b9-62d7-4d70-b959-3a06e4327d46.html

(3)http://topic.csdn.net/u/20070712/07/57c7cfc6-7314-400d-86d2-230a72581ea5.html



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值