Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1774384
  • 博文数量: 198
  • 博客积分: 4088
  • 博客等级: 上校
  • 技术积分: 2391
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-15 16:29
个人简介

游戏开发,系统架构; 博客迁移到:http://www.jianshu.com/u/3ac0504b3b8c

文章分类

全部博文(198)

文章存档

2017年(1)

2016年(12)

2015年(1)

2014年(3)

2013年(13)

2012年(18)

2011年(150)

分类: C/C++

2013-07-15 12:47:40

关于delete 和 delete[]函数

1. C++标准的说法,对于非数组类型,分配函数是operator new,释放函数是operator delete。对于数组类型,分配函数operator new[],释放函数是operator delete[]。 

If the allocated type is a non-array type, the allocation function’s name is operator new and the deallocation function’s name is operator delete. If the allocated type is an array type, the allocation function’s name is operator new[] and the 
deallocation function’s name is operator delete[]. 

new操作符的作用是先调用分配函数分配足够的内存以便容纳所需类型的对象,再调用构造函数初始化内存中的对象。 delelte操作符相反,是先调用析构函数,再调用释放函数释放内存。注意这里 new operatoroperator new的区别,一个是new操作符,一个是分配函数。 

2. 当在应该使用delete[]的地方使用了delete, delete的地方使用了delelte[],不仅仅会造成内存泄漏,有可能会造成程序错误,先看一个例子
为了了解new, new[], delete, delete[]做了些什么,我们先重载分配函数和释放函数。只是一个简单实现,如果按C++标准还要抛出bad_alloc异常什么的。 
void* operator new (size_t size) 

    void *p=malloc(size); 
    printf("Adress after malloc: %p; size: %u\n",p,size); 
    return p; 


void* operator new[] (size_t size) 

    void *p=malloc(size); 
    printf("Adress after malloc: %p; size: %u\n",p,size); 
    return p; 

void operator delete[] (void* p) 

    printf("Adress before free: %p\n",p); 
    free(p); 


void operator delete (void *p) 

   printf("Adress before free: %p\n",p); 
    free(p); 


2.1 先来看看应该使用delete[]的地方使用了delete的情况 
class   A 

    public: 
        A() { printf("A::A()\n");} 
        ~A() { printf("A::~A()\n");} 
}; 

在这个例子中,本来应该用delelte[]的,而我们错误的使用了delete. 
int   main() 

    A   *a   =   new   A[1]; 
    printf("Adress after new[] = %p\n", a); 
    delete   a; 
    return 0; 



运行结果: 
Adress after malloc: 0x804a008; size: 5 //分配内存 
A::A()                                  //调用构造函数 
Adress after new[] = 0x804a00c           
A::~A()                                 //调用析构函数 
Adress before free: 0x804a00c           //释放内存 
*** glibc detected *** ./test2: free(): invalid pointer: 0x0804a00c *** 
从结果中,我们可以看出,当new A[1]执行的时候,会先调用operator new[],operator new[]函数又调用了malloc,注意,new A[1]返回的地址(0x804a00c)不是malloc返回的地址(0x804a008),而是+4返回,隐匿了4个字节(这里堆从低地址向高地址增加)。而这个被修改的地址传给了delete,delete直接又把这个地址传给了free. 因为malloc得到的地址和free释放的地址,不一样,程序也就出错了。 
另外,从malloc调用时的size看,也比下面的例子多了4个字节(size = 5 vs. size = 1),这个被隐匿起来的4字节干什么用的,我们后面在说。 

2.2 再来看该用delete,却使用了delete[]的情况。 
int   main() 

    A *a   =   new A; 
    printf("Adress after new = %p\n", a); 
    delete[] a; 
    return 0; 


运行结果: 
Adress after malloc: 0x804a008; size: 1 
A::A() 
Adress after new = 0x804a008 
A::~A() 
... 
Adress before free: 0x804a004 
*** glibc detected *** ./test2: free(): invalid pointer: 0x0804a004 *** 
这次mallocnew返回的地址是一样的了(0x804a008),问题却出在free[]free[]得到的地址是0x804a008,但它在调用operator delete[]的时候却自己把这个地址-4,变成了0x804a004。当free执行的时候,同样因为不是malloc分配的,从而程序出错。 

下次我们再说这隐藏的四个字节用来干什么了,以及内置类型的deletedelete[]有什么区别。

3. 下面我们来看看上面提到的4字节是干什么用的。 
int   main() 

    A *a   =   new   A[100]; 
    printf("Adress after new = %p\n", a); 
    printf("number of A at %p  = %d\n", (int*)a-1, *((int*)a-1)); 
    printf("size of a at %p = %d\n", (int*)a-2, *((int*)a-2)); 
    delete[] a; 
    return 0; 


运行结果: 
Adress after malloc: 0x804a008; size: 104 
A::A() 
... 
Adress after new = 0x804a00c 
number of A at 0x804a008  = 100 
size of a at 0x804a004 = 113 
A::~A() 
... 
Adress before free: 0x804a008 

从上面的运行结果可以看出,那个被隐藏起来的四字节放的是数组元素的个数(number of A at 0x804a008  = 100)。当使用new[]的时候,new[]会多分配4个字节,用来存放数组的个数,然后把malloc返回的地址+4给用户使用。而在free[]的时候,free[]会把得到的地址-4,得到内存中对象的个数,然后把这个地址交于free释放。如果我们把这个值修改了如 *((int*)a-1) = 50。我们就会发现,上面的程序调用了100次构造函数,但是只调用了50次析构函数,因为delete[]以为只有50个数组元素。 
其实malloc自身也隐匿了一些内存空间用于自身管理,如malloc返回的地址-4,可以看到malloc分配的空间大小。如果我们把这个值修改了,如:*((int*)a-2) = 0, 程序也会出错。关于malloc这里就不多说了,可以看看相关的资料或者Linux版上我写的帖子。 

4. 关于内置类型和没有析构函数的类类型,无论是delete, delete[]都不会出错。 
class   A 

    public: 
        A() { printf("A::A()\n");} 
//         ~A() { printf("A::~A()\n");} 
}; 

int   main() 

    A   *a   =   new   A[1]; 
    printf("Adress after new[] = %p\n", a); 
    delete   a; 
    return 0; 

运行结果: 
Adress after malloc: 0x804a008; size: 1 
A::A() 
Adress after new = 0x804a008 
Adress before free: 0x804a008 

可以看出new []返回的地址就是malloc的地址,同理,free[]传递给free的地址也一样。所以free的时候不会有问题。编译器在遇到没有析构函数的类的时候,就认为对象中没有需要额外释放的内存和处理的数据。所以在既不影响程序正确执行有没有内存泄漏的情况下,直接释放对象所占用的内存就好了。 
注意:这完全是编译器优化的结果,并不能保证程序在所有环境下都能正确执行。 

以上所有程序用gcc (GCC) 4.1.0编译 

小结
1. 无论什么类型,用new[]分配的用delet[]释放,new分配的用delete释放。 
2. new[]delete[]的时候会使用隐藏的内存来记录一些数组信息,如数组元素个数。这造成了错误的使用了deletedelete[]的时候,会造成程序错误。 
3. 对于内置类型和没有析构函数的类类型,编译器可能对其进行了优化,造成deletedelete[]都不会出错,或者内存泄漏。但这是不符合标准,且依赖于具体实现的。

转自:

阅读(1281) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~