delete 和 delete []

每一本 C++ 教材上都会告诉你,使用new,new [],delete 和 delete [] 的时候必须要配对使用,否则会造成严重的后果。那么,到底会有什么严重的后果呢?
先说一下 C/C++ 中的内存释放的机制。当 C++ 程序从空余内存块中找出分配出 size 大小的内存并且使用完之后,在释放这块内存的时候,程序如何得知你当初申请了多大的内存呢?事实上,当使用 malloc/new/new[] 申请了一块内存的时候,编译器实际上会在这块内存的头部保存一个 size_t 信息,记录了这块内存的大小。这个size信息是需要占用额外的空间的,也就是说:

int * p = new int[10];

这行语句实际上从系统空闲内存中索取了sizeof(int) * 10 + sizeof (size_t) 大小的空间,用图形表示的话大概是这个样子:

系统的内存分布

在对 p 指针进行free/delete/delete []操作的时候,实际上会先通过 *(p-sizeof(size_t))来获得这块内存的大小,然后将内存归还给系统。从这一步上来说,free,delete,delete [] 没有任何区别——也就是说,对于下面的两行代码:

int * p = new int[10];
delete p;

虽然没有使用delete [],但内存是可以正确地被归还的,不会引起内存泄漏之类的后果(但是,某些比较 powerful 的编译器可能会报错)。恰好相反,如果你觉得delete 没有 delete [] 强劲,试图通过一个循环归还内存的话:

int * p = new int[10];
for (int i = 0; i < 10; ++i) {
    delete p+i;
}

则会引发不可预知的后果,因为:1,这个时候整个数组都已经归还了,无需再次归还;2,delete试图读取要删除的指针头部的size信息,对于p[1] 及之后的指针来说,前面的一块内存里存放了什么样的值完全是无法预料的。同样地,企图删除 new [] 申请的数组中的某一个元素也是非法的:

int * p = new int[10];
delete p + 5;  // WRONG!

既然 delete 和 delete [] 都能正确地归还内存,那么这两者又有什么区别呢?为什么 new/delete、new []/delete [] 必须配对使用?
实际上是,对于C++程序,delete 不仅肩负着归还内存的任务,还肩负着正确地析构对象的任务。所以,如果用伪代码表示的话,delete [] 的行为类似于:

for (int i = 0 ; i < memory_size; i = i + sizeof(int)) {
    *((void * )p + i)->~int();  // 这只是个示例,很多编译器会针对POD对象进行优化,把这一步省略掉
}
free(p);

简而言之,先循环地对数组中的对象都调用一次析构函数,最后再把内存归还给系统。所以,如果对象存在析构函数并且析构函数肩负着比较重要的任务,例如释放资源句柄之类的,则必须要使用 delete [];使用 delete 的话,虽然这块内存会正确地归还,但只会针对数组内的第一个对象执行析构函数,后续的对象的析构函数都无法被执行。

以上是 delete 和 delete [] 的区别,下面是一些奇技淫巧。

我们知道 C++ 中存在着类继承,对于一个继承体系下的类的对象,delete 和 delete [] 是怎么执行的呢?

class Base {
    private:
        int a;
    public:
        virtual ~Base() {
            cout << "Good bye, Base!" << endl;
        }
};

class Derived : public Base {
    private:
        int b;
    public:
        virtual ~Derived() {
            cout << "Good bye, Derived!" << endl;
        }
};

对于下面的两行代码:

Base * p = new Derived();
delete p;

对虚函数有了解的人都会知道,在delete p;的时候,会执行Derived类的析构函数,即使p指针是一个Base类型的指针,这是由C++的虚函数机制来保证的。那么问题来了:下面两行代码,执行结果是什么?

Base * p = new Derived[10];
delete [] p;

即使new []和 delete [] 正确地配对使用了,这段程序的运行依然是不正确的,会出现内存访问错误。问题就在于,在执行 delete [] p的时候,实际上是这么执行的:

for (int i = 0; i < memory_size; i = i + sizeof(Base)) {
    (Base *)((void * ) p + i)->~();
}
free(p);

实际上,在对数组中的第一个对象执行析构操作的时候,结果是正常的:通过虚表指针找到了虚表,再通过虚表找到了派生类的虚析构函数并且执行了这个虚析构函数;但是在对第二个对象执行析构操作的时候,由于Base对象和Derived对象的大小并不相同,(Base *)((void * ) p + i)并不能找到虚表的地址——这个指针实际指向的并不是第二个Derived对象的开头,而是第一个Derived对象结尾的几个字节!程序会错误地把这末尾的几个字节的内容当做虚表的地址,所以会引发内存访问错误。
C++允许程序员完全地掌控你所写的程序(甚至允许你嵌入汇编!),但是你必须搞清楚你所写的是什么。任何一个不恰当地操作都可能引发灾难性的后果,不会有 exception 让你去 catch,而是直接 down 掉整个程序。这门语言迷人和迷惑人的地方就在这儿——从语言本身的特性来讲,C++大概真的是复杂度最高的编程语言了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容