new/delete 和new[]/delete[]的解读(转)



new 和 delete 到底是什么?

如果找工作的同学看一些面试的书,我相信都会遇到这样的题:sizeof 不是函数,然后举出一堆的理由来证明 sizeof 不是函数。在这里,和 sizeof 类似,new 和 delete 也不是函数,它们都是 C++ 定义的关键字,通过特定的语法可以组成表达式。和 sizeof 不同的是,sizeof 在编译时候就可以确定其返回值,new 和 delete 背后的机制则比较复杂。
继续往下之前,请你想想你认为 new 应该要做些什么?也许你第一反应是,new 不就和 C 语言中的 malloc 函数一样嘛,就用来动态申请空间的。你答对了一半,看看下面语句:

string *ps = new string("hello world");

你就可以看出 new 和 malloc 还是有点不同的,malloc 申请完空间之后不会对内存进行必要的初始化,而 new 可以。所以 new expression 背后要做的事情不是你想象的那么简单。在我用实例来解释 new 背后的机制之前,你需要知道 operator new 和 operator delete 是什么玩意。

operator new 和 operator delete

这两个其实是 C++ 语言标准库的库函数,原型分别如下:

void *operator new(size_t);     //allocate an object
void *operator delete(void *);    //free an object

void *operator new[](size_t);     //allocate an array
void *operator delete[](void *);    //free an array

后面两个你可以先不看,后面再介绍。前面两个均是 C++ 标准库函数,你可能会觉得这是函数吗?请不要怀疑,这就是函数!C++ Primer 一书上说这不是重载 new 和 delete 表达式(如 operator= 就是重载 = 操作符),因为 new 和 delete 是不允许重载的。但我还没搞清楚为什么要用 operator new 和 operator delete 来命名,比较费解。我们只要知道它们的意思就可以了,这两个函数和 C 语言中的 malloc 和 free 函数有点像了,都是用来申请和释放内存的,并且 operator new 申请内存之后不对内存进行初始化,直接返回申请内存的指针。

我们可以直接在我们的程序中使用这几个函数。

new 和 delete 背后机制

知道上面两个函数之后,我们用一个实例来解释 new 和 delete 背后的机制:

我们不用简单的 C++ 内置类型来举例,使用复杂一点的类类型,定义一个类 A:

class A
{
public:
    A(int v) : var(v)
    {
        fopen_s(&file, "test", "r");
    }
    ~A()
    {
        fclose(file);
    }

private:
    int var;
    FILE *file;
};

很简单,类 A 中有两个私有成员,有一个构造函数和一个析构函数,构造函数中初始化私有变量 var 以及打开一个文件,析构函数关闭打开的文件。

我们使用

class *pA = new A(10);

来创建一个类的对象,返回其指针 pA。如下图所示 new 背后完成的工作:

简单总结一下:

  1. 首先需要调用上面提到的 operator new 标准库函数,传入的参数为 class A 的大小,这里为 8 个字节,至于为什么是 8 个字节,你可以看看《深入 C++ 对象模型》一书,这里不做多解释。这样函数返回的是分配内存的起始地址,这里假设是 0x007da290。
  2. 上面分配的内存是未初始化的,也是未类型化的,第二步就在这一块原始的内存上对类对象进行初始化,调用的是相应的构造函数,这里是调用 A:A(10); 这个函数,从图中也可以看到对这块申请的内存进行了初始化,var=10, file 指向打开的文件
  3. 最后一步就是返回新分配并构造好的对象的指针,这里 pA 就指向 0x007da290 这块内存,pA 的类型为类 A 对象的指针。

所有这三步,你都可以通过反汇编找到相应的汇编代码,在这里我就不列出了。

好了,那么 delete 都干了什么呢?还是接着上面的例子,如果这时想释放掉申请的类的对象怎么办?当然我们可以使用下面的语句来完成:

delete pA;

delete 所做的事情如下图所示:

delete 就做了两件事情:

  1. 调用 pA 指向对象的析构函数,对打开的文件进行关闭。
  2. 通过上面提到的标准库函数 operator delete 来释放该对象的内存,传入函数的参数为 pA 的值,也就是 0x007d290。

好了,解释完了 new 和 delete 背后所做的事情了,是不是觉得也很简单?不就多了一个构造函数和析构函数的调用嘛。

如何申请和释放一个数组?

我们经常要用到动态分配一个数组,也许是这样的:

string *psa = new string[10];      //array of 10 empty strings
int *pia = new int[10];           //array of 10 uninitialized ints

上面在申请一个数组时都用到了 new [] 这个表达式来完成,按照我们上面讲到的 new 和 delete 知识,第一个数组是 string 类型,分配了保存对象的内存空间之后,将调用 string 类型的默认构造函数依次初始化数组中每个元素;第二个是申请具有内置类型的数组,分配了存储 10 个 int 对象的内存空间,但并没有初始化。

如果我们想释放空间了,可以用下面两条语句:

delete [] psa;
delete [] pia;

都用到 delete [] 表达式,注意这地方的 [] 一般情况下不能漏掉!我们也可以想象这两个语句分别干了什么:第一个对 10 个 string 对象分别调用析构函数,然后再释放掉为对象分配的所有内存空间;第二个因为是内置类型不存在析构函数,直接释放为 10 个 int 型分配的所有内存空间。

这里对于第一种情况就有一个问题了:我们如何知道 psa 指向对象的数组的大小?怎么知道调用几次析构函数?

这个问题直接导致我们需要在 new [] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

还是用图来说明比较清楚,我们定义了一个类 A,但不具体描述类的内容,这个类中有显示的构造函数、析构函数等。那么 当我们调用

class A *pAa = new A[3];

时需要做的事情如下:

从这个图中我们可以看到申请时在数组对象的上面还多分配了 4 个字节用来保存数组的大小,但是最终返回的是对象数组的指针,而不是所有分配空间的起始地址。

这样的话,释放就很简单了:

delete [] pAa;

这里要注意的两点是:

  • 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
  • 传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。

为什么 new/delete 、new []/delete[] 要配对使用?

其实说了这么多,还没到我写这篇文章的最原始意图。从上面解释的你应该懂了 new/delete、new[]/delete[] 的工作原理了,因为它们之间有差别,所以需要配对使用。但偏偏问题不是这么简单,这也是我遇到的问题,如下这段代码:

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

这肯定是没问题的,但如果把 delete []pia; 换成 delete pia; 的话,会出问题吗?

这就涉及到上面一节没提到的问题了。上面我提到了在 new [] 时多分配 4 个字节的缘由,因为析构时需要知道数组的大小,但如果不调用析构函数呢(如内置类型,这里的 int 数组)?我们在 new [] 时就没必要多分配那 4 个字节, delete [] 时直接到第二步释放为 int 数组分配的空间。如果这里使用 delete pia;那么将会调用 operator delete 函数,传入的参数是分配给数组的起始地址,所做的事情就是释放掉这块内存空间。不存在问题的。

这里说的使用 new [] 用 delete 来释放对象的提前是:对象的类型是内置类型或者是无自定义的析构函数的类类型!

我们看看如果是带有自定义析构函数的类类型,用 new [] 来创建类对象数组,而用 delete 来释放会发生什么?用上面的例子来说明:

class A *pAa = new class A[3];
delete pAa;

那么 delete pAa; 做了两件事:

  • 调用一次 pAa 指向的对象的析构函数;
  • 调用 operator delete(pAa); 释放内存。

显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。

上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放 pAa 指向的内存空间,这个总是会造成严重的段错误,程序必然会奔溃!因为分配的空间的起始地址是 pAa 指向的地方减去 4 个字节的地方。你应该传入参数设为那个地址!

同理,你可以分析如果使用 new 来分配,用 delete [] 来释放会出现什么问题?是不是总会导致程序错误?

总的来说,记住一点即可:new/delete、new[]/delete[] 要配套使用总是没错的





一.new的用法:

1. new() 分配这种类型的一个大小的内存空间,并以括号中的值来初始化这个变量;

2. new[] 分配这种类型的n个大小的内存空间,并用默认构造函数来初始化这些变量;


例子:

#include

#include

using namespace std;

int main(){

char * p=new char("Hello");

//error分配一个char(1字节)的空间,

//用"Hello"来初始化,这明显不对



char* p=new char[6];

//p="Hello";

//不能将字符串直接赋值给该字符指针p,原因是:

//指针p指向的是字符串的第一个字符,只能用下面的

//strcpy

strcpy(p,"Hello");

cout<<*p<<endl; //只是输出p指向的字符串的第一个字符!

cout<<p<<endl; //输出p指向的字符串!

delete[] p;

return 0;

}

输出结果:

H

Hello

3.开辟单变量地址空间

1)new int; //开辟一个存放数组的存储空间,返回一个指向该存储空间的地址.int *a = new int 即为将一个int类型的地址赋值给整型指针a.

2)int *a = new int(5) 作用同上,但是同时将整数赋值为5

4.开辟数组空间

一维: int *a = new int[100];开辟一个大小为100的整型数组空间

二维: int **a = new int[5][6]

三维及其以上:依此类推.

一般用法: new 类型 [初值]

5. 当使用new运算符定义一个多维数组变量或数组对象时,它产生一个指向数组第一个元素的指针,返回的类型保持了除最左边维数外的所有维数。例如:

int *p1 = new int[10];

返回的是一个指向int的指针int*

int (*p2)[10] = new int[2][10];

new了一个二维数组, 去掉最左边那一维[2], 剩下int[10], 所以返回的是一个指向int[10]这种一维数组的指针int (*)[10].

int (*p3)[2][10] = new int[5][2][10]; new了一个三维数组, 去掉最左边那一维[5], 还有int[2][10], 所以返回的是一个指向二维数组int[2][10]这种类型的指针int (*)[2][10].




new运算符

最常用的是作为运算符的new,比如:

string *str = new string(“test new”);

作为运算符,new和sizeof一样,是C 内置的,你不能对它做任何的改变,除了使用它。

new会在堆上分配一块内存,并会自动调用类的构造函数。

new函数

第二种就是new函数,其实new运算符内部分配内存使用的就是new函数,原型是:

void *operator new(size_t size);

new函数返回的是一个void指针,一块未经初始化的内存。如你所见,这和C语言的malloc行为相似,你可以重载new函数,并且增加额外的参数,但是必须保证第一个参数必须是size_t类型,它指明了分配内存块的大小,C 允许你这么做,当然一般情况下这是不必要的。如果重载了new函数,在使用new操作符时调用的就是你重载后的new函数了。

如果使用new函数,和语句string *str = new string(“test new”)相对的代码大概是如下的样子:

1. string *str = (string*)operator new(sizeof(string));

2. str.string(“test new”);

3. // 当然这个调用时非法的,但是编译器是没有这个限制的

这还不算完,还有第三种的new存在。

placement new

第三种,placement new,这也是new作为函数的一种用法,它允许你在一块已存在的内存上分配一个对象,而内存上的数据不会被覆盖或者被你主动改写,placement new同样由new操作符调用,调用格式是:

new (buffer) type(size_t size);

先看看下面的代码:

4. char str[22];

5. int data = 123;

6. int *pa = new (&data) int;

7. int *pb = new (str) int(9);

结果*pa = 123(未覆盖原数据),而*pb = 9(覆盖原数据),可以看到placement new 并没有分配新的内存,也可以使用在栈上分配的内存,而不限于堆。

为了使用placement new 你必须包含或者

其实placement new和第二种一样,只不过多了参数,是函数new的重载,语法格式为:

void *operator new(size_t, void* buffer);

它看起来可能是这个样子:

void *operator new(size_t, void* buffer) { return buffer;}

和new对应的就是delete了,需要回收内存啊,不然就泄漏了,这个下次再写吧,回忆一下今天的内容先。

二.delete用法:

1. int *a = new int;

delete a; //释放单个int的空间

2.int *a = new int[5];

delete [] a; //释放int数组空间

要访问new所开辟的结构体空间,无法直接通过变量名进行,只能通过赋值的指针进行访问.

用new和delete可以动态开辟,撤销地址空间.在编程序时,若用完一个变量(一般是暂时存储的数组),下次需要再用,但却又想省去重新初始化的功夫,可以在每次开始使用时开辟一个空间,在用完后撤销它.



总结

1. 函数new

void *operator new(size_t size); 在堆上分配一块内存,和placement new(void *operator new(size_t, void* buffer)); 在一块已经存在的内存上创建对象,如果你已经有一块内存,placement new会非常有用,事实上,它STL中有着广泛的使用。

2. 运算符new

最常用的new,没什么可说的。

3. 函数new不会自动调用类的构造函数,因为它对分配的内存类型一无所知;而运算符new会自动调用类的构造函数。

4. 函数new允许重载,而运算符new不能被重载。




【1】malloc与free  和 new与delete

(1)malloc与free是C++/C语言的标准库函数。new/delete是C++的运算符。它们都可以申请动态内存和释放内存。

(2)对于非内部数据类型的对象而言,用malloc/free无法满足动态对象的要求(对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数)。

(3)由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此,C++语言需要可以完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

(4)都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象。new会自动调用对象的构造函数。

delete 会调用对象的destructor,而free 不会调用对象的destructor。

【2】描述内存分配方式以及它们的区别?

(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如:全局变量,static 变量。

(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

(3)从堆上分配(动态内存分配)。程序在运行的时候用malloc 或 new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。

动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

【3】malloc 与 free

(1)malloc函数分配的空间一定要用free函数释放掉。

(2)free(p) 仅仅指释放了malloc分配的空间,但是p指针仍然不为空,所以,在free函数释放后一般要置空!防止野指针!

(3)非空指针只可以释放一次。

(4)一般两者搭配使用。

(5)malloc 与 free示例代码如下:

复制代码
 1 #include<iostream>
 2 #include<malloc.h>
 3 using namespace std;
 4 
 5 void main()
 6 {
 7     int *p = NULL ;  //指针定义最好初始化为空(程序员基本素养,哈哈)
 8 
 9     //空指针释放多次没有任何意义
10     free(p);   //编译通过!运行通过!一次
11     free(p);   //编译通过!运行通过!二次
12 
13     p = (int *)malloc(sizeof(int)*5);
14     if(NULL == p)
15     {
16         cout<<"malloc failed!"<<endl;
17         exit(1);
18     }
19     else
20     {
21         p[0] = 0;       //注意赋值形式
22         p[4] = 4;
23 //        p[5]=100;       //编译可以通过,但是运行错误error!!!因为p[5]越界
24 
25         cout<<"p[4]: "<<p[4]<<endl;   //4
26           
27         cout<<"p[1]: "<<p[1]<<endl;   //随机数!!
28      }
29 
30     free(p);  //malloc申请空间使用free释放(固定搭配)
31 
32     if(NULL == p)
33     {
34         cout<<"free(p) p == NULL"<<endl;
35     }
36     else
37     {
38         cout<<"free(p) p != NULL"<<endl;   //野指针!!!
39 //        p[2] = 100;  //编译可以通过,运行时崩溃!!因为野指针所致。
40     }
41 
42 //    free(p);    //编译可以通过,运行时崩溃!! 因为已经释放了一次,再次释放导致错误。
43 
44     p = NULL;  //彻底预防了它的破坏性
45 
46     //........
复制代码

【4】new 与 delete

(1)new的三种形态

到目前为止,C++相关资料书籍谈及的new至少代表以下三种含义:

<1>new operator : new 运算符 (当然,书面称法。个人觉得还是按照习惯称作关键字new,以下此种形态均称关键字 new

<2>operator new : 操作符  new(当然,书面称法。个人觉得称为new函数,以下此种形态均称new函数

<3>placement new: 定位 new(C++primer上的称法)

(2)关键字new

平常我们使用最多的就是关键字new。它由语言内建 ,不能重载 ,不能改变其行为 。

关键字new在堆上动态创建一个对象时,它实际上做了三件事:

1:获得一块内存空间、

2:调用构造函数、

3:返回正确的指针。

当然,如果我们创建的是内置类型的变量,那么第二步会被省略。

示例代码如下:

复制代码
 1 #include<iostream>
 2 #include<assert.h>
 3 using namespace std;
 4 
 5 class A 
 6 { 
 7     int i; 
 8 public: 
 9     A(int _i = 2) :i(_i*_i) 
10     {
11         cout<<"constructor   "<<this<<endl;
12     } 
13     void Print() 
14     { 
15         cout<<i<<endl; 
16     } 
17     ~A()
18     {
19         cout<<"destructor  "<<this<<endl;
20     }
21 
22 }; 
23 
24 void main()
25 {
26     /*
27      *内置类型示例代码
28      */
29     int *p1 = NULL;
30     p1 = new  int(10);
31     assert(p1 != NULL);
32     cout<<*p1<<endl;     //10
33     delete  p1;
34     p1 = NULL;
35 
36 
37     int *p2 = NULL;
38     p2 = new  int[5];   //申请5份int类型大小的空间
39     assert(p2 != NULL);
40     delete []p2;       //释放数组变量
41     p2 = NULL;
42 
43     /*
44     *自定义类型示代码
45     */
46     A  *p3 = NULL;
47     p3 = new A;       //调用默认复合构造函数
48     assert(p3 != NULL);
49     p3->Print();      //4
50     cout<<"delete obj"<<endl;
51     delete p3;
52     p3 = NULL;
53 
54     A  *p4 = NULL;
55     p4 = new A[5];       //调用默认复合构造函数   注意数组
56     assert(p4 != NULL);
57     p4[0].Print();    //4
58     cout<<"delete obj"<<endl;
59     delete []p4;
60     p4 = NULL;
61 
62     A  *p5 = NULL;
63     p5 = new A(10);       //调用默认复合构造函数   注意区别
64     assert(p5 != NULL);
65     p5[0].Print();    //100
66     cout<<"delete obj"<<endl;
67     delete p5;
68     p5 = NULL;
69 
70 }
71 
72 /*
73 10
74 constructor   00644B48
75 4
76 delete obj
77 destructor  00644B48
78 constructor   00644BDC
79 constructor   00644BE0
80 constructor   00644BE4
81 constructor   00644BE8
82 constructor   00644BEC
83 4
84 delete obj
85 destructor  00644BEC
86 destructor  00644BE8
87 destructor  00644BE4
88 destructor  00644BE0
89 destructor  00644BDC
90 constructor   00644B48
91 100
92 delete obj
93 destructor  00644B48
94  */
复制代码

(3)函数new

关键字new第一步分配内存实际上是通过调用new函数来完成的,而这里的new就是像加减乘除一样的操作符,因此是可以重载的。

new函数默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回;如果失败,则转而去调用一个new_hander,然后继续重复前面过程。

如果我们对这个过程不满意,就可以重载operator new,来设置我们希望的行为。

示例代码如下:

复制代码
  1 #include<iostream>
  2 #include<assert.h>
  3 #include<new>
  4 using namespace std;
  5 
  6 class A 
  7 { 
  8     int i; 
  9 public: 
 10     A(int _i = 2) :i(_i*_i) 
 11     {
 12         cout<<"constructor   "<<this<<endl;
 13     } 
 14     void Print() 
 15     { 
 16         cout<<i<<endl; 
 17     } 
 18     ~A()
 19     {
 20         cout<<"destructor  "<<this<<endl;
 21     }
 22 
 23 }; 
 24 /*
 25  *重载全局new/delete函数
 26  */
 27 void * operator new(size_t size)
 28 {
 29     cout<<" overload operator new "<<endl;
 30     void *p = malloc(size);
 31     return (p);
 32 }
 33 void operator delete(void *p)
 34 {
 35     cout<<" overload operator delete "<<endl;
 36     free(p);
 37 }
 38 
 39 void main()
 40 {
 41     /*
 42      *内置类型示例代码
 43      */
 44     int *p1 = NULL;
 45     p1 = (int *)::operator new(sizeof(int));
 46     new(p1) int(10);   //第一种赋值方式  
 47     assert(p1 != NULL);
 48     cout<<*p1<<endl;       //10
 49     ::operator delete(p1);
 50     p1 = NULL;
 51 
 52 
 53     int *ptr = NULL;
 54     ptr = (int *)::operator new(sizeof(int));
 55     *ptr = 100;    //第二种赋值方式  
 56     assert(ptr != NULL);
 57     cout<<*ptr<<endl;      //100
 58     delete  ptr;
 59     ptr = NULL;
 60 
 61     int *p2 = NULL;
 62     p2 = (int *)::operator new(sizeof(int) * 5);   //申请5份int类型大小的空间
 63     assert(p2 != NULL);
 64     for(int i = 0; i < 5; ++i)
 65     {
 66         p2[i] =  i + 10;   //数组变量的赋值
 67     }
 68     for(int i = 0; i < 5; ++i)
 69     {
 70         cout<<p2[i]<<endl;   //10 11 12 13 14 
 71     }
 72     ::operator delete[] (p2);       //释放数组变量
 73     p2 = NULL;
 74 
 75     /*
 76     *自定义类型示代码
 77     */
 78     A  *p3 = NULL;
 79     p3 = (A *)::operator new(sizeof(A));     
 80     assert(p3 != NULL);
 81     new(p3) A(10);
 82     p3->Print();       //100
 83     cout<<"delete obj"<<endl;
 84     p3->~A();    //先调用对象析构函数
 85     ::operator delete(p3);  //再释放申请内存
 86     p3 = NULL;
 87 
 88     //注意差别
 89     A  *p4 = NULL;
 90     p4 = (A *)::operator new(sizeof(A));     
 91     assert(p4 != NULL);
 92     new(p4) A(10);
 93     p4->Print();       //100
 94     cout<<"delete obj"<<endl;
 95     ::operator delete(p4);  //直接释放申请内存
 96     p4 = NULL;
 97 }
 98 
 99 /*
100 overload operator new
101 10
102 overload operator delete
103 overload operator new
104 100
105 overload operator delete
106 overload operator new
107 10
108 11
109 12
110 13
111 14
112 overload operator new
113 constructor   00194B48
114 100
115 delete obj
116 destructor  00194B48
117 overload operator delete
118 overload operator new
119 constructor   00194B48
120 100
121 delete obj
122 overload operator delete
123 
124  */
复制代码

下面比较new关键字与new函数的区别:

<1>new关键字

int *ptr = new  int(100);

1:分配内存; 2:赋初始值; 3:类型自动匹配; 4:大小自动。

<2>new函数

int *ptr = (int *)::operator new(sizeof(int) * 5);

1:分配内存; 2:无初始化; 3:类型转换; 4:大小手动。

(4)定位new

定位new是用来实现定位构造的,因此可以实现关键字new三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象(变量)的内存后,在这块内存上构造一个对象(变量)。

示例代码如下:

关于对象的构建,上面new函数的示例代码中已经很具体了。在此,特别示例定位new也可以构造栈上的内存。

复制代码
 1 #include<iostream>
 2 #include<assert.h>
 3 //#include<new.h>   //有些资料书提醒必须加这个头文件,VS2010下可以省略。
 4 using namespace std;
 5 
 6 class A 
 7 { 
 8     int i; 
 9 public: 
10     A(int _i = 2) :i(_i*_i) 
11     {
12         cout<<"constructor   "<<this<<endl;
13     } 
14     void Print() 
15     { 
16         cout<<i<<endl; 
17     } 
18     ~A()
19     {
20         cout<<"destructor  "<<this<<endl;
21     }
22 
23 }; 
24 
25 
26 void main()
27 {
28     char s[sizeof(A)]; 
29     A* p = (A*)s; 
30     new(p) A(3);   //定位new的用法
31     p->Print(); 
32     p->~A();  //不过必须要显式调用析构函数
33 }
34 
35 /*
36 constructor   0038F8EC
37 9
38 destructor  0038F8EC
39  */
复制代码

这里“new(p)   A(3)”这种奇怪的写法即是定位new的用法,它实现了在指定内存空间用指定类型的构造函数来构造一个对象的功能,后面A(3)就是对构造函数的显式调用。

通过上面的例子以及这个例子,我们可以看到这块指定的地址既可以是栈,又可以是堆,定位new对此不加区分。

但是,除非特别必要,不要直接使用定位new ,这毕竟不是用来构造对象的正式写法,只不过是new函数的一个步骤而已。

使用关键字new地编译器会自动生成对定位new的调用的代码,因此也会相应的生成使用delete时调用析构函数的代码。

如果是像上面那样在栈上使用了定位new,则必须手工调用析构函数,这也是显式调用析构函数的唯一情况: p->~A();

当我们觉得默认的关键字new对内存的管理不能满足我们的需要,而希望自己手工的管理内存时,定位new就有用了。

STL中的allocator就使用了这种方式,借助定位new来实现更灵活有效的内存管理。 

【5】new的基本使用指南

(1)如果想在堆上建立一个对象,应该用关键字new 。它既分配内存又为对象调用构造函数。

(2)如果仅仅想分配内存,就应该调用 new 函数;它不会调用构造函数。

如果想定制在堆对象被建立时的内存分配过程,你应该写自己的new 函数,然后使用new关键字, new 关键字会调用定制的 operator new .

(3)如果想在一块已经获得指针的内存里建立一个对象,应该用 定位new 。定位new 主要适用于:

<1>在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;

<2>长时间运行而不被打断的程序;

<3>以及执行一个垃圾收集器(garbage collector)。

 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值