new与delete

【1】malloc与free  和 new与delete

(1)malloc与free是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 <cstdlib>
 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     //........
47     system("pause");
48 }
49 
50 // run out:
51 /*
52 p[4]: 4
53 p[1]: -842150451
54 free(p) 后 p != NULL
55 请按任意键继续. . .
56 */

【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 <cassert>
 3 using namespace std;
 4 
 5 class A 
 6 { 
 7     int i;
 8 
 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 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     system("pause");
71 }
72 
73 // run out:
74 /*
75 10
76 constructor   00514890
77 4
78 delete obj
79 destructor  00514890
80 constructor   00514924
81 constructor   00514928
82 constructor   0051492C
83 constructor   00514930
84 constructor   00514934
85 4
86 delete obj
87 destructor  00514934
88 destructor  00514930
89 destructor  0051492C
90 destructor  00514928
91 destructor  00514924
92 constructor   00514890
93 100
94 delete obj
95 destructor  00514890
96 请按任意键继续. . .
97  */

(3)函数new

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

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

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

示例代码如下:

  1 #include <iostream>
  2 #include <cassert>
  3 using namespace std;
  4 
  5 class A 
  6 { 
  7     int i;
  8 
  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 
 34 void operator delete(void *p)
 35 {
 36     cout << " overload operator delete " << endl;
 37     free(p);
 38 }
 39 
 40 void main()
 41 {
 42     /*
 43      * 内置类型示例代码
 44      */
 45     int *p1 = NULL;
 46     p1 = (int *)::operator new(sizeof(int));
 47     new(p1) int(10);   // 第一种赋值方式  
 48     assert(p1 != NULL);
 49     cout << *p1 << endl;     // 10
 50     ::operator delete(p1);
 51     p1 = NULL;
 52 
 53 
 54     int *ptr = NULL;
 55     ptr = (int *)::operator new(sizeof(int));
 56     *ptr = 100;    // 第二种赋值方式  
 57     assert(ptr != NULL);
 58     cout << *ptr << endl;      // 100
 59     delete ptr;
 60     ptr = NULL;
 61 
 62     int *p2 = NULL;
 63     p2 = (int *)::operator new(sizeof(int) * 5);   // 申请5份int类型大小的空间
 64     assert(p2 != NULL);
 65     for (int i = 0; i < 5; ++i)
 66     {
 67         p2[i] =  i + 10;   // 数组变量的赋值
 68     }
 69     for (int i = 0; i < 5; ++i)
 70     {
 71         cout << p2[i] << endl;   // 10 11 12 13 14 
 72     }
 73     ::operator delete[] (p2);       // 释放数组变量
 74     p2 = NULL;
 75 
 76     /*
 77     * 自定义类型示例代码
 78     */
 79     A *p3 = NULL;
 80     p3 = (A *)::operator new(sizeof(A));     
 81     assert(p3 != NULL);
 82     new(p3) A(10);
 83     p3->Print();       // 100
 84     cout << "delete obj" << endl;
 85     p3->~A();    // 先调用对象析构函数
 86     ::operator delete(p3);  // 再释放申请内存
 87     p3 = NULL;
 88 
 89     // 注意差别
 90     A *p4 = NULL;
 91     p4 = (A *)::operator new(sizeof(A));     
 92     assert(p4 != NULL);
 93     new(p4) A(10);
 94     p4->Print();       // 100
 95     cout << "delete obj" << endl;
 96     ::operator delete(p4);  // 直接释放申请内存
 97     p4 = NULL;
 98 
 99     system("pause");
100 }
101 
102 // run out:
103 /*
104  overload operator new
105 10
106  overload operator delete
107  overload operator new
108 100
109  overload operator delete
110  overload operator new
111 10
112 11
113 12
114 13
115 14
116  overload operator new
117 constructor   00434890
118 100
119 delete obj
120 destructor  00434890
121  overload operator delete
122  overload operator new
123 constructor   00434890
124 100
125 delete obj
126  overload operator delete
127 请按任意键继续. . .
128  */

以上这段代码建议最好调试逐步看看,详细分析一下运行结果,然后认真总结一下。

下面比较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是用来实现定位构造的,因此可以实现关键字new三步操作中的第二步:

也就是,在取得了一块可以容纳指定类型对象(变量)的内存后,在这块内存上构造一个对象(变量)。

示例代码如下:

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

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

这里“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)。

 

Good Good Study, Day Day Up.

顺序  选择  循环  坚持  总结

作者:kaizen
声明:本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此声明,且在文章明显位置给出本文链接,否则保留追究法律责任的权利。
签名:顺序 选择 循环
原文地址:https://www.cnblogs.com/Braveliu/p/2854211.html