`
kmplayer
  • 浏览: 512680 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

释放对象数组:delete与delete[]

阅读更多
<<c++ primer>>练习   14.11   中提到:
Account   *parray=new   Account[100];
delete   parray;
delete   []   parray;
方括号的存在会使编译器获取数组大小(size)然后析构函数再被依次应用在每个元素上,一共size次。否则,只有一个元素被析构。
无论哪种情况,分配的全部空间被返还给自由存储区。

我的问题是:为什么无论哪种情况,分配的全部空间被返还给自由存储区?
对于delete parray,为什么不是删除单个Account元素,而是删除了100个.
编译器怎么知道parray这个指针实际指向的是数组还是单个元素,即便知道指向的是自由存储区的数组,这个数组的大小又怎么知道。
难道是编译器辅助行为?

总结:空间释放(肯定有个log记录分配的大小)和调用析构函数(类型识别,不同的编译器实现不同)采用不同的机制.

(1)一般在分配时分配器会自动写一个日志(一般在分配使用得内存之前又一个结构),用于记录分配的大小,分配内容的sizeof等等。
直观得想想,delete和delete[]都是传入一个void*如果不保存日志就无法知道分配时到底是分配了一个还是多个单元.
所以虽然delete和delete[]不同但是分配器在执行释放过程中都会读取这个日志,从而了解到底应该释放多少内存,但是从程序员的角度来说,既然分配了数组,就应该用delete[]

(2)在VC下用汇编跟过delete[]的执行情况,发现这个 "日志 "就是一个4字节长的整数记录数组元素个数,紧挨在数组第一个元素之前.  
但是有个前提:对象类型(或其基类)有显式析构函数.换句话说,析构函数是非trivial的.  
否则的话,数组前面是没有这个日志的.其实对于析构函数是trivial的情况,delete[]时无需调用其析构函数,因此此时VC把delete[]当做delete同样处理.

(3)因为释放数组空间和为数组调用析构函数是两个独立的部分,可以使用不同的机制来实现。
释放空间的机制是需要绝对保证的。因此,即使你不写delete[],它也会将所有空间释放,其机制可以是前置的长度信息,也可以不是(如后置的特征分割符等等)。
而调用析构函数可以一般采用前置长度信息的方式(当然也可以有其他方式)。在没有[]提示时,编译器在调用析构就将它当一个元素,而不会使用数组方式来调用每一个析构函数了。
LS:“但是有个前提:对象类型(或其基类)有显式析构函数.换句话说,析构函数是非trivial的. 否则的话,数组前面是没有这个日志的.”
——这说明,LS使用的编译器在释放数组空间时,并没有用前置的长度信息的方式。由此可见,释放数组空间和为数组调用析构函数确实可以使用不同的机制

(4)delete parray,编译器得到类型信息是Account单个的指针,那么释放时,只调用一次析构函数。
delete[] parray,编译器得到的类型信息是Account[]类型,则按照Account数组来处理,依次调用每个元素的析构函数。

注意,以上是在编译期间就确定下来的,编译器识别到类型信息的不同会决定调用析构函数的情况有不同。

但是对于内存释放,delete操作则不是通过类型信息来确定分配的内存大小,那么内存大小的信息从什么地方得到呢?
当我们使用operator new为一个自定义类型对象分配内存时,实际上我们得到的内存要比实际对象的内存大一些,这些内存除了要存储对象数据外,还需要记录这片内存的大小,此方法称为   cookie。这一点上的实现依据不同的编译器不同。(例如   MFC   选择在所分配内存的头部存储对象实际数据,而后面的部分存储边界标志和内存大小信息。g++   则采用在所分配内存的头4个自己存储相关信息,而后面的内存存储对象实际数据。)当我们使用   delete   operator   进行内存释放操作时,delete   operator   就可以根据这些信息正确的释放指针所指向的内存块。
对于parray指针,可以根据这样的cookie信息来得到指向内存空间的大小,delete parray和delete[] parray都是一样的,同样一个指针,cookie信息是相同的,所以对应的内存都会被释放掉。但是由于编译器理解两种情况下的类型是不同的,所以调用析构函数会有不同。

(5)难道是编译器辅助行为?
没错,就是。不同的编译器可能采用的具体方法有可能不一样,但不管采用什么方法,编译器必须记住那块大小。

(6)转自<<effective c++>>
条款5:对应的new和delete要采用相同的形式

下面的语句有什么错?
string *stringarray = new string[100];
delete stringarray;
一切好象都井然有序——一个new对应着一个delete——然而却隐藏着很大的错误:程序的运行情况将是不可预测的。至少,stringarray指向的100个string对象中的99个不会被正确地摧毁,因为他们的析构函数永远不会被调用。
用new的时候会发生两件事。首先,内存被分配(通过operator new 函数,详见条款7-10和条款m8),然后,为被分配的内存调用一个或多个构造函数。用delete的时候,也有两件事发生:首先,为将被释放的内存调用一个或多个析构函数,然后,释放内存(通过operator delete 函数,详见条款8和m8)。对于 delete来说会有这样一个重要的问题:内存中有多少个对象要被删除?答案决定了将有多少个析构函数会被调用。
这个问题简单来说就是:要被删除的指针指向的是单个对象呢,还是对象数组?这只有你来告诉delete。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组:
string *stringptr1 = new string;
string *stringptr2 = new string[100];
...
delete stringptr1;// 删除一个对象
delete [] stringptr2;// 删除对象数组
如果你在stringptr1前加了"[]"会怎样呢?答案是:那将是不可预测的;
如果你没在stringptr2前没加上"[]"又会怎样呢?答案也是:不可预测。
int这样的固定类型来说,结果也是不可预测的,即使这样的类型没有析构函数。所以,解决这类问题的规则很简单:如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]。

#include <iostream>

using namespace std;

struct foo
{
    ~foo(){}; //去掉后,就不会记录个数了.
};


int main(int argc,char *argv[])
{

     foo* f = new foo;
     delete f;
     f=0;
     delete f;

     foo* fa = new foo[8];
     printf("%u\n", *((char *)fa - 4)); //输出8:辅助析构函数.
     /*
     对于有显式析构函数的对象的数组,
     编译器会在数组前分配4个字节储存数组元素的个数
     (也就是需要调用析构函数的次数),
     因为必须知道数组实际的元素合个数,
     delete[]才能知道需要调用几次析构函数。
     */
     delete fa; //有的编译器这里会有异常.
     //用delete而不是delete[]释放用new[]分配的空间这种行为是undefined的,
     //也就是由编译器实现所决定的
     fa = 0;
     delete fa;

     return 0;
}
分享到:
评论

相关推荐

    vc++6.0中动态数组实现(例如string类对象数组等)[文].pdf

    2. 动态分配的数组可以在自由存储区中创建和释放。 3. 动态数组可以使用 new 和 delete 表达式来实现。 动态数组的定义: 动态数组的定义需要指定类型和数组长度,但不需要为数组对象命名。例如: ```cpp int *pia...

    C++Builder动态对象数组

    ### C++Builder动态对象数组:TList类、DynamicArray与STL中的Vector容器 #### TList 类 在C++Builder中,TList是用于管理动态对象数组的一种方式。它提供了较为简便的方式来创建、管理和销毁一系列对象。TList...

    自学考试c++知识点总结.doc

    `,释放对象数组:`delete[] 指针名;` - 需要注意的是,`new`和`delete`必须配对使用,并且`delete`操作会调用对象的析构函数。 2. 引用数据类型: - 引用是一种别名,如:`int a = 56; int &b = a;`,此时`b`是`...

    vc++6.0中动态数组实现(例如string类对象数组等).pdf

    删除动态分配的数组时,应使用`delete[]`,而不是`delete`,这是因为`new[]`和`delete[]`成对出现,用于管理数组内存的分配和释放: ```cpp delete [] pia; // 释放之前分配的int数组 delete [] psa; // 释放之前...

    编程题3_1对象数组初始化[借鉴].pdf

    对象数组初始化和 Student 类设计 在软件开发中,对象数组初始化是一种常见的编程技术,特别是在设计学生信息管理系统时。在本文中,我们将讨论如何设计 Student 类,实现对象数组的初始化和成员函数的使用。 ...

    详解C++中的对象指针与对象数组

    - **动态分配对象数组**:使用`new`运算符可以在运行时动态分配对象数组,如`Time *arr = new Time[5]`,不过别忘了在不再需要时使用`delete[]`释放内存。 在实际编程中,对象指针和对象数组的应用非常广泛,特别...

    C++ new/delete相关知识点详细解析

    一、new和delete创建和释放动态数组:数组类型的变量有三个重要的限制:数组长度固定,在编译时必须知道其长度,数组只在定义它的语句内存在。动态数组:长度固定,编译时不必知道其长度,通常是运行时确定;一直...

    12_delete和delete[]的区别1

    但对于自定义数据类型,释放对象数组时,务必使用`delete[]`,因为它会确保调用所有对象的析构函数,防止内存泄漏。而使用`delete`仅适用于释放由`new`分配的单个对象的内存,或者对于那些不需要执行析构函数的基本...

    C++数组与指针深入剖析

    - 指针:可以使用动态内存分配函数(如`new`和`delete`)来分配和释放内存。 #### 六、总结 数组和指针在C++编程中都是极其重要的工具。理解它们之间的联系和区别对于编写高质量的代码至关重要。通过本文的学习,...

    C++中new/delete与new[]/delete[]的使用区别

    5. **构造和析构**:当使用`new[]`分配对象数组时,每个对象都会调用构造函数;释放时,`delete[]`会调用每个对象的析构函数,确保正确清理资源。 总之,`new`和`delete`适用于单个对象,而`new[]`和`delete[]`适用...

    C++调用cjson解析数组

    在IT领域,尤其是在软件开发...总结来说,C++调用cjson库解析JSON数组涉及包含库头文件、解析JSON字符串、访问数组、遍历数组元素以及释放内存等步骤。在实际应用中,还需要根据具体JSON结构和业务需求进行适配和扩展。

    读new、delete、指向连续空间的指针、数组、空间释放、空间申请[C++][内存管理] 有感

    本文将深入探讨`new`、`delete`操作符,以及如何处理指向连续空间的指针、数组和内存释放与申请。通过理解这些基本概念,开发者可以更好地控制程序的内存使用,防止内存泄漏,并优化性能。 首先,我们来了解`new`和...

    c++指针数组.rar

    一、指针与数组的基础知识 1. 指针:在C++中,指针是一个变量,它存储了另一个变量的地址。通过指针,我们可以间接访问和修改被指向的变量。指针的声明通常包含类型和指针名,如`int* p`表示一个指向整型变量的指针...

    2022年自考c++知识点总结.docx

    * 使用delete关键字释放对象数组占用内存:delete []指针名 * 注意:new和delete必须成对出现,且delete释放对象时,会调用析构函数。 引用数据类型 * 引用数据类型的定义:别名 = 对象名 * 示例:int a = 56; int...

    动态内存分配操作符new和delete

    注意,使用`new[]`分配的内存需要使用`delete[]`来释放,而不是简单的`delete`,因为`delete`只适用于单个对象,而`delete[]`负责释放整个数组。 `delete`和`delete[]`操作符: 当不再需要动态分配的内存时,必须...

    CPP-array.zip_数组 C++

    这里创建了一个3x5的二维数组,但需要注意,释放这样的内存需要两次`delete`: ```cpp delete[] ptr; // 释放外层的指针数组 ptr = nullptr; // 防止悬挂指针 ``` 在"动态数组源文件"中,可能包含了以上概念的实践...

    2022年自考c++知识点总结.doc

    * 使用delete[]关键字释放对象数组占用的内存,例如:delete [] 指针名; * 注意:new和delete必须成对出现,且delete释放对象时,会调用析构函数。 二、引用 * 引用是一种别名,例如:int a = 56; int &b = a; 则b...

    C++动态内存分配(new/new[]和delete/delete[])详解

    delete运算符用于释放单个对象,而delete[]运算符用于释放对象数组。例如: ```cpp delete fp; // 释放一个 Foo 对象 delete[] fpa; // 释放一个 Foo 对象数组 ``` 在使用new和delete运算符时,需要注意以下几点: ...

Global site tag (gtag.js) - Google Analytics