new 和 delete 都做了什么 (C++)
new & delete 过程
当我们使用关键字 new 在堆上动态创建一个对象A时,比如 A* p = new A() ,它实际上做了三件事:
1.向堆上申请一块内存空间(做够容纳对象A大小的数据)( operator new )
2.使用 static_cast 进行类型转换
3.调用构造函数 (调用A的构造函数(如果A有的话))( placement new)
4.返回正确的指针。
当我们使用 delete 的时候,也是一样的,其行为如下:
-
定位到指针p所指向的内存空间,然后根据其类型,调用其自带的析构函数(内置类型不用)
-
然后释放其内存空间(调用 operator delete ,将这块内存空间标志为可用,然后还给操作系统)
-
将指针标记为无效(指向
NULL
)。
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
operator new 和operator new功能都是仅仅分配内存,底层调用了 malloc 函数。 operator new是给new用的,operator new[]是给new[]用的。而且可以被重载。比如可以重载该函数,实现全局调用的打 log。
void * operator new(size_t size)
{
cout << "call operator new " << endl;
return ::operator new(size);
}
数组怎么办?
调用 operator new [] 函数后,C++ 的做法是在分配数组空间时,在头部多分配了 4 个字节的大小,专门保存数组的大小。当然,编程时拿到的是对象数组的指针,而不是所有分配空间的起始地址。
调用 operator delete [] 函数后:根据从数组对象指针前面的 4 个字节中取出的值,进行依次析构,这里要注意:
- 传入 operator delete [] 函数的参数不是数组对象的指针 p,而是 p 的值减 4。(因为做减法之后,才是真正的分配空间时的起始地址)
为什么 new/delete 、new []/delete[] 要配对使用?
我们看看如果是带有自定义析构函数的类类型,用 new []
来创建类对象数组,而用 delete 来释放会发生什么?用上面的例子来说明:
class A *pAa = new class A[3];
delete pAa;
那么 delete pAa;
做了两件事:
- 调用一次 pAa 指向的对象的析构函数;
- 调用
operator delete(pAa);
释放内存。
显然,这里只对数组的第一个类对象调用了析构函数,后面的两个对象均没调用析构函数,如果类对象中申请了大量的内存需要在析构函数中释放,而你却在销毁数组对象时少调用了析构函数,这会造成内存泄漏。
上面的问题你如果说没关系的话,那么第二点就是致命的了!直接释放 pAa 指向的内存空间,这个总是会造成严重的段错误,程序必然会崩溃!因为分配的空间的起始地址是 pAa 指向的地方减去 4 个字节的地方。这里应该传入参数设为那个地址!
placement new
有时候你真的会想直接调用一个构造函数,去针对一个已经被定义的对象调用其构造函数生成对象,但这没有什么意义,因为构造函数用来对对象进行初始化,而一个对象只能只能初始化一次。但是你偶尔会有一些分配好的原始内存,你想要在上面构建已知的对象,这样的话,你就需要用到 placement new。placement new 是用来实现定位构造的,因此可以实现 new operator 三步操作中的第二步,也就是在取得了一块可以容纳指定类型对象的原始内存后,在这块内存上构造出一个对象有点类似于
char s[sizeof(string)];
string* p = (string*)s;
new(p) string("memory"); //p->string::string("memory");