delete的原理(辨析性质的笔记,原内容转载,笔记是原创)

1.用法篇
耗尽内存

尽管现代机器的内存容量越来越大,但是自由存储区总有可能被耗尽。如果程序用完了所有可用的内存,new 表达式就有可能失败。如果 new 表达式无法获取需要的内存空间,系统将抛出名为 bad_alloc 的异常。我们将在第 6.13 节介绍如何抛出异常。

撤销动态创建的对象

 

动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间。

     delete pi;

frees the memory associated with the int object addressed by pi.

该命令释放 pi 指向的 int 型对象所占用的内存空间。

 


如果指针指向不是用 new 分配的内存地址,则在该指针上使用 delete 是不合法的。

 

C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针。下面提供了一些安全的和不安全的 delete expressions 表达式。

     int i;
     int *pi = &i;
     string str = "dwarves";
     double *pd = new double(33);
     delete str; // error: str is not a dynamic object
     delete pi;  // error: pi refers to a local
     delete pd;  // ok

 

值得注意的是:编译器可能会拒绝编译 str 的 delete 语句。编译器知道 str 并不是一个指针,因此会在编译时就能检查出这个错误。第二个错误则比较隐蔽:通常来说,编译器不能断定一个指针指向什么类型的对象,因此尽管这个语句是错误的,但在大部分编译器上仍能通过。


零值指针的删除

如果指针的值为 0,则在其上做 delete 操作是合法的,但这样做没有任何意义:

     int *ip = 0;
     delete ip; // ok: always ok to delete a pointer that is equal to 0

 

C++ 保证:删除 0 值的指针是安全的。

在 delete 之后,重设指针的值

 

执行语句

     delete p;


后,p 变成没有定义。在很多机器上,尽管 p 没有定义,但仍然存放了它之前所指向对象的地址,然而 p 所指向的内存已经被释放,因此 p 不再有效。

删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。


一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。


const 对象的动态分配和回收

 

C++ 允许动态创建 const 对象:

     // allocate and initialize a const object
     const int *pci = new const int(1024);

 

与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改。上述 new 表达式返回指向 int 型 const 对象的指针。与其他 const 对象的地址一样,由于 new 返回的地址上存放的是const 对象,因此该地址只能赋给指向 const 的指针。

对于类类型的 const 动态对象,如果该类提供了默认的构造函数,则此对象可隐式初始化:

     // allocate default initialized const empty string
     const string *pcs = new const string;

 

new 表达式没有显式初始化 pcs 所指向的对象,而是隐式地将 pcs 所指向的对象初始化为空的 string 对象。内置类型对象或未提供默认构造函数的类类型对象必须显式初始化。

警告:动态内存的管理容易出错

 

下面三种常见的程序错误都与动态内存分配相关:

  1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。

  2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。

  3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。

操纵动态分配的内存时,很容易发生上述错误,但这些错误却难以跟踪和修正。


删除 const 对象

 

尽管程序员不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的:

     delete pci; // ok: deletes a const object

 

即使 delete 表达式的操作数是指向 int 型 const 对象的指针,该语句同样有效地回收 pci 所指向的内容。


#########################################################################################################

2.c++实现原理篇

下面的发帖总结一下是:

1.new[]会在申请到的内存前面加入一些信息表示这申请到的内存被划分为了多少单元(即new出的数组有多长),这个信息只用于确定调用多少次析构函数时用到,在确定释放多大内存空间时用不到,因为系统自然知道要释放的内存大小是多少,系统在内存分配表中有记录。

2.delete和delete[]都是会正确的把全部的内存释放掉的,但是delete只会调用数组第一个元素的析构函数,delete[]会调用全部数组单元的析构函数,这就用到了上一条中new[]存储的数组长度,delete[]利用这个长度确定在哪些地方调用多少次析构函数。

3.delete和delete[]只在调用析构函数上有差别,在释放内存上是一样的,而且两者释放内存时为了确定要释放多长的内存它们都是通过查系统的内存分配表来实现的,1中说道的长度仅仅在delete[]确定调用多少析构函数时用到。因为管理内存是操作系统的事(因此操作系统知道这内存分配了多少),但调用析构函数则是编译器的事(因此编译器需要知道要调用多少次,这就是1中提到的new[]做的事)

4.通过上面三条,我们知道delete和delete[]只有在面对自定义对象数组时才有差别,面对系统内置对象时是无差别的,即:

int* p = new int[10],那么delete p和delete[] p是没任何差别的。


发表于: 2012-10-05 15:01:24
我在学校c++的时候一直不明白,为什么会设计出 delete和delete[]两种操作;
因为大家都知道
1,int *p = new int 是分配一个单元,int *p = new int[100]是分配100个单元,
2,delete p 是删除释放一个单元,delete [] p 是释放 多个单元,具体的数据目是查系统的分配表得到的;
3,那么既然可以查表,为什么不直接让delete 和 delete[]合并为一个操作,具体释放多少查表得到既可以了,

可能是设计师考虑到了什么东西了吧,比如效率等,
但是我想不出来,请大家帮我解释一下


你问的很好,许多人不知道对于内置类型来说,delete和delete []是一样的,看起来你是知道的

原因如下,事实上new和delete都是两步操作,分配(释放)堆内存和调用构造(析构)函数

对于堆内存的释放,确实不需要做额外的事情,delete和delete []无差别

区别就在于调用析构函数这一步,如果用delete只会调用第一个对象的析构,只有调用delete[]才会调用全部的析构


引用 1 楼  的回复:
你问的很好,许多人不知道对于内置类型来说,delete和delete []是一样的,看起来你是知道的

原因如下,事实上new和delete都是两步操作,分配(释放)堆内存和调用构造(析构)函数

对于堆内存的释放,确实不需要做额外的事情,delete和delete []无差别

区别就在于调用析构函数这一步,如果用delete只会调用第一个对象的析构,只有调用delete[]才会调……

讲的太好了,我测试了一下;

C/C++ code ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
 
using  namespace  std;
 
class  A
{
public :
         A(){cout<< "A\n" ;}
         ~A(){cout<< "~A\n" ;}
};
 
int  main()
{
         A *p= new  A[10];
         delete  p;
}


g++编译输出是:

XML/HTML code ?
1
2
3
4
5
6
7
8
9
10
11
A
A
A
A
A
A
A
A
A
A
~A



语言是发展中的,早期C++的delete []中还让你填你申请的单位个数呢,确实如你所说现在代码中如果全写delete[]就可以了,那是不是以后会向这方面发展,就说不清了


回复于: 2012-10-05 16:00:38
引用 11 楼  的回复:
语言是发展中的,早期C++的delete []中还让你填你申请的单位个数呢,确实如你所说现在代码中如果全写delete[]就可以了,那是不是以后会向这方面发展,就说不清了
难道是历史残留问题导致的??

用delete[] 删除非数组的内存貌似不可以吧。
delete[]会根据申请内存的前一些字节来判断申请了多少内存,然后释放掉。
用new[]会根据申请的内存数在申请空间前面添加申请的空间数,而new则不会。

如果用delete[]来删除new申请的内存的话,会出问题吧。


语言越智能效率越低下...
不是不可以
c++设计者完全可以按你的要求设计出...
就像现在很多语言一样, 相比C++ 智能多了,... 
但是代价就是效率的降低...


回复于: 2012-10-06 21:58:21
引用 26 楼  的回复:
delete[]就相当于
for(;;)delete

5条裤衩哟,别犯这种错误,你试试new[]一次然后循环delete看看,会崩的很惨哦


回复于: 2012-10-07 11:18:51
引用 6 楼  的回复:
但是还是没有解决我的问题啊;
因为调用析构函数的时候,已经知道有多少个对象了,
为什么不全部调用呢;还分为一个和多个这两种情况

这个问题,C++之父在《C++语言的设计和演化》里的解释就是:效率

malloc/free步骤是交给C和系统去做的,系统不区分单个和数组,你就是想区分也没用,反正这不是C++的事,所以之父对此没什么可折腾的

但是析构多少个元素,系统是不管的,这需要编译器做,这就需要考量了。析构是C++的特点,C没有的。之父需要负责的是做同样的事情时,C++的效率不低于C

毫无疑问,数组总是要多一些信息的,如果都把单个理解为size为1的数组,程序员是方便了,但实现效率上是有累赘的

这点累赘值不值得考虑,那就取决于之父的设计原则和时代背景了


回复于: 2012-10-08 11:24:41
引用 40 楼  的回复:
如果是二维数组,你该如何delete呢?请问(如果不循环的话?)

你现在问的和之前说的完全是两个问题

其实你之前应该这么说
delete相当于:
dtor
free


delete[]相当于:
for(;;) dtor;
free


而你之前说的却是
delete[]相当于:
for(;;){dtor;free;}


回复于: 2012-10-08 13:03:47
引用 45 楼  的回复:
我认为主要是为了分配和释放要配对, 运算符new/delete是可以重载的。这意味着可以任意多样的资源(包括内存,但不限于内存)管理策略,比如placement delete,只有对象的析构,没有释放内存到堆, 再比如placement new, 没有从堆申请存储。如果统一, 显然就失去了更多的灵活性。

并不存在placement delete

如果要配对的话,就应该允许显式调用构造函数

其实这个问题就是效率/复杂性的问题,在《C++语言的设计和演化》有直接谈到的

为了降低复杂性,最初甚至要求程序员写出delete的元素确切个数,即delete [n] p;

折中之后,后来的版本只需要告诉编译器是一个还是多个,即delete p;或者delete [] p;


回复于: 2012-10-08 15:47:53
引用 11 楼  的回复:
语言是发展中的,早期C++的delete []中还让你填你申请的单位个数呢,确实如你所说现在代码中如果全写delete[]就可以了,那是不是以后会向这方面发展,就说不清了

在c++2.0之前,将数组的大小提供给delete,是程序员的责任,比如

A* pA = new A[5];
delete[5] pA;

到了c++2.1,可以使用

delete[] pA;

这归功于编译器对数组维度的计算。但这项工作会严重地冲击程序效率。因此,这被交给程序员来决定是否在代码中增加这个[]。

除此之外,delete[]会调用数组对象的每个destructor,而delete只调用第一个。


回复于: 2012-10-08 16:29:26
引用 51 楼  的回复:
引用 11 楼 的回复:

语言是发展中的,早期C++的delete []中还让你填你申请的单位个数呢,确实如你所说现在代码中如果全写delete[]就可以了,那是不是以后会向这方面发展,就说不清了


在c++2.0之前,将数组的大小提供给delete,是程序员的责任,比如

A* pA = new A[5];
delete[5] pA;

到了c++2.1,可以使用

……

所谓冲击程序效率是从抽象的角度说的,而且基本没有现实意义,本身堆内存自己就维护了数组大小,C语言的free从诞生第一天起就从没让人传过什么size,各种操作系统的资源分配释放也没见过释放的时候指定大小的,因为分配的时候就维护了大小可以说是行规,谁不这样做谁SB

“程序员来决定是否在代码中增加这个[]”根本就没意义,我之前说了,一个对象数组,你用delete也好,用delete[]也好,其结果都是 所有的对象都不能用了,因为堆被整体释放了,留下一堆野指针对象的析构函数不释放,完全没意义。为了一点点的性能,造成内存泄漏的大bug,根本不值,更何况这些泄漏的内存理论上就无法重新利用。 程序员根本没有决定权,数组必须用delete[]释放,否则就是错误代码

为什么许多人,包括帖子里的一些网友都有“delete[] 等于 循环delete”的错误认识,其实很正常,只有这样delete和delete[]同时存在才能讲得通,大家自然会这么想。

所以,我认为这个问题就是C++发展过程中的一个bug,对某些性能问题考虑过多(99.9%的程序根本不在乎释放时的这点性能开销,而人家真正对性能要求如此严苛的程序根本不会理睬你C++的“好意”,直接上C),结果越搞越乱,规避这个bug最有效的手段就是如教材上所说的new与delete严格对应,new[]与delete[]严格对应。


回复于: 2012-10-08 17:08:08
程序员来决定是否在代码中增加这个[],其实这个意思是说:把pA是个class object还是个object array的判断,交给程序员来做。

因为,在c++早期,complier对于delete[]的计算开销还是比较大的,如果不管是object还是array,程序员都使用

delete[] x;

compiler也会对单一object的释放产生开销比较大的数组计算,这不值得,也不能推出这样的编译器。


当然,这是一个历史遗留的问题,但如果没有历史,又怎么会有现在?


回复于: 2012-10-09 16:55:14
引用 47 楼  的回复:
引用 45 楼  的回复:我认为主要是为了分配和释放要配对, 运算符new/delete是可以重载的。这意味着可以任意多样的资源(包括内存,但不限于内存)管理策略,比如placement delete,只有对象的析构,没有释放内存到堆, 再比如placement new, 没有从堆申请存储。如果统一, 显然就失去了更多的灵活性。

并不存在placement delete

如果要配对的……

并不存在placement delete

如果要配对的话,就应该允许显式调用构造函数

其实这个问题就是效率/复杂性的问题,在《C++语言的设计和演化》有直接谈到的

为了降低复杂性,最初甚至要求程序员写出delete的元素确切个数,即delete [n] p;

折中之后,后来的版本只需要告诉编译器是一个还是多个,即delete p;或者delete [] p;

这个观点我同意,我曾在一本书上看过。

其实,有几个概念是有区别
1. operator new 与new operator 是有区别的;
2. operator array new 与array new operator是有区别;

看一看c++中的内存管理,就会很清楚的弄明白其中的区别;
operator new 其实就是平时用的new关键字, 
其实用new在生成一个对象,做了如下工作:
 a.调用标准的内存分配操作符,new operator,进行对象实例所需的内存分配;
 b. 调用对象的构造函数初始化,该对象实例;

具体细节,不在分析了;

推荐一本书 《C++ 必知必会》 对上述问题做了清楚的说明;
从里面可以找到常见的对C++ 的疑问。

所以其 英文名字: the C++ common knowledge.


回复于: 2012-10-10 16:50:22
因为有私货。 比如数组 int a[10], 在地址a的前面可能有cookei,记录了数组的长度等信息。 不同的编译器实现方式不同。

class Test{ ... };

Test* p1 = new Test;
Test* p2 = new Test[10];

Test* p=0;

p=p1;
delete  p;   //编译器怎么知道p对应了1个对象需要析构?

p=p2;
delete [] p;  //编译器怎么知道p对应了N个对象需要析构,还有一定是精确的10个对象需要析构?

[]就是提示编译器去找cookei, 根据cookei做处理







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值