C++ 使用new与delete需注意的原则

时间:2022-09-04 21:24:35

C++的动态内存管理是通过new和delete两个操作来完成的,即用new来申请空间,用delete来释放空间。在使用new和delete时,注意以下原则。

1.new与delete需一一对应

用new操作申请空间,如果申请成功,必须在以后的某个时刻用delete释放该空间,既不能忘记释放,也不能多次释放。前者会引起内存泄露,后者会引起运行时错误。如下面的程序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;
 
int main()
{
    int *p;
    p=new int(3);
    if(p)
    {
        delete p;
    }
    delete p;
    return 0;
}

以上程序对指针p所指向的空间进行两次释放,这种内存错误对C++程序危害极大,也是很多人对C++忘而却步的原因。多次释放同一块内存空间,并不一定立即引起程序运行错误,也不一定会导致程序运行的崩溃,这跟具体的编译器实现有关。但是,多次释放同一块内存空间绝对是一个编程错误,这个编程错误可能会在其后的某个时刻导致其他的逻辑错误的发生,从而给程序的调试和纠错带来困难。考察如下程序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
 
int main()
{
    int *p,*q,*one;
    one=new int;
    if(one)
    {
        cout<<one<<endl;
    }
    delete one;
    p=new int(3);
    if(p)
    {
        cout<<p<<endl;
    }
    delete one;//假设这句语句是程序员不小心加上的
    q=new int(5);
    if(q)
    {
        cout<<q<<endl;
    }
    cout<<(*p)+(*q)<<endl;
    delete p;
    delete q;
}

程序通过编译,运行结果如下:

003289A0
003289A0
003289A0
10

程序运行过程中会产生中断。从程序的输出可以看出,在将指针one所指向的空间释放后,为指针p申请的空间就是原来one所指向的空间。由于不小心在为p分配空间之后再次使用了delete one,导致q申请到的空间就是原来p所申请的空间,这样赋给*q的值就改写了原来p所指向的单元的值,导致最后输出结果为10。由此可知,多次释放同一块内存空间,即使不导致程序运行中断,也会破坏环境,使指针与所对应的空间的隶属关系出现混乱,从而导致逻辑错误。在大型程序设计中,这种逻辑错误的查找会变得十分费时费力。

**注意:**当指针p的值为NULL时,多次使用delete p并不会带来麻烦,因为释放空指针的空间实际上不会导致任何操作。所以,将“不用”的指针设置为NULL是一个好的编程习惯。

2.new[]与delete[]需一一对应

在申请对象数组时,需要使用new[]运算符,与之对应,释放对象数组时,需要使用delete[]运算符。这一点与C语言有所区别,C中无论申请单个还是多个对象,均使用malloc()/free()函数。首先看一下delete与delete[]运算符的区别。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test
{
public:
    Test() { cout<<"ctor"<<endl; }
    ~Test() { cout << "dtor" << endl; }
};
 
//segment1
Test* pArray1 = new Test[3];
delete pArray1;
 
//segment2
Test* pArray2 = new Test[3];
delete[] pArray2;

其中代码片段segment1运行结果如下:

ctor
ctor
ctor
dtor

segment2运行结果如下:

ctor
ctor
ctor
dtor
dtor
dtor

可以看出,delete与delete[]区别在于释放对象数组时,delete只调用了一次析构函数,delete[]调用了三次析构函数,完成了对象数组的释放。实际上,在使用new和new[]申请内存空间时,会申请一段额外的内存来保存用户申请的内存空间大小,元素个数等信息。当使用delete[]释放内存空间时,会逐个调用对象的析构函数并完成最终的内存空间的释放。使用delete释放对象数组时,则只会调用单个对象的析构函数,造成内存泄漏。符号[]告诉编译器,在delete一块内存时,先去获取内存保存的元素个数,然后一一清理。所以使用delete释放new[]申请的内存空间和使用delete[]释放new申请的内存空间都错误的做法。

具体使用时,需要注意以下两点:

 (1)对于内置数据类型,因为没有构造和析构函数,所以使用delete和delete[]的效果是一样的。比如:

?
1
2
3
int* pDArr=new int[3];
//processing code
delete pDArr;   //等同于delete[] pDArr

对于内置数据类型,虽然可以使用delete完成对象数组内存空间的释放,但是为了保证代码的可读性,建议使用delete[]来完成。所以,new[]与delete[]使用时应一一对应。

(2)对于经常使用typedef的程序员来说,很容易new[]与delete的混用,例如有如下操作:

?
1
2
typedef int Height[NUM];
int* pHeight=new Height;

这个情况应该使用delete还是delete[]呢?答案如下:

?
1
2
delete  pHeight;        //wrong,但容易错误地使用delete
delete[] pHeight;   //right

为了避免出现上面的错误,建议不要对数组使用typedef,或者采用STL中的vector代替数组。

3.构造函数中的new/new[]与析构函数的中delete/delete[]需一一对应

当类的成员中有指针变量时,在构造函数中用new申请空间并且在析构函数中用delete释放空间是一种“标准的”、安全的做法。例如下面的程序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;
 
class Student
{
    char* name;
public:
    Student()
    {
        cout<<"Default constructor"<<endl;
    }
    Student(char*);
    ~Student();
};
 
Student::Student(char*s)
{
    //Student();//此句运行时报错,构造函数不能调用其他构造函数
    cout<<"In constructor,allocating space"<<endl;
    name=new char[strlen(s)+1];
    strcpy(name,s);
    cout<<"name:"<<name<<endl;
}
 
Student::~Student()
{
    cout<<"In destructor, free space"<<endl;
    delete name;
}
 
int main()
{
    Student s1("张三");
}

程序运行输出:

In constructor,allocating space
name:张三
In destructor, free space

由于任何一个对象,其构造函数只调用一次,其析构函数也只调用一次,这样就能保证运行时new和delete操作是一一对应的,也就保证了内存管理的安全性。

在C++中,一个构造函数不能调用本类的另一个构造函数,其原因就是为了防止构造函数的相互调用打破了内存申请与释放之间的这种对应关系。

以上就是C++ 使用new与delete需注意的原则的详细内容,更多关于C++ new与delete的资料请关注服务器之家其它相关文章!

原文链接:https://cloud.tencent.com/developer/article/1394365