C/C++中基本的内存管理工具
new操作符以及相应的操作符函数
new/delete,new[]/delete[]
对于new/delete,new[]/delete[],他们均为C++的运算符。
他们的使用:
string *sp = new string;//单个对象
string *arr = new string[10];//10个对象
delete sp;
delete[] arr;
当我们使用一个new表达式时,他实际上执行了三个步骤:
1. new 表达式调用一个operator new(operator new[])的标准库函数,请求分配一块内存空间。
2. 将分配的空间(void * )转为对应的对象指针。
3. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值。
而当我们使用一条delete 表达式删除一个对象时,它背后实际上执行了两步操作:
1. 调用对象对应的析构函数
2. 调用名为operator delete(或者 operator delete[])的标准库函数释放内存空间。
测试代码:
\\测试代码
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
//默认构造函数
Complex():a(0),b(0){
cout<<"default Complex constructor"<<endl;
};
//有参数的构造函数
Complex(const int &i,const int &j = 0):a(i),b(j){
cout<<"Complex constructor"<<endl;
}
//析构函数
~Complex(){
cout<<"Complex destructor"<<endl;
};
};
void* operator new(size_t size){
cout<<"operator new"<<endl;
return malloc(size);
}
void* operator new[](size_t size){
cout<<"operator new[]"<<endl;
return malloc(size);
}
void operator delete(void *p){
cout<<"operator delete"<<endl;
return free(p);
}
void operator delete[](void *p){
cout<<"operator delete[]"<<endl;
return free(p);
}
int main(){
Complex* p0 = new Complex;
Complex* p1 = new Complex[10];
delete(p0);
delete[](p1);
system("pause");
return 0;
}
placement new
调用形式如下:
new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size]{ braced initializer list}
其中place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。(这个new不分配空间)
当我们使用一个**placement new(定位new)*时,定位new使用operator new(size_t,void)"分配"它的内存。这是一个我们无法自定义的operator new版本。该函数不分配任何内存,它只是简单的返回指针实参;然后由new表达式负责在指定的地址初始化对象以完成整个工作。定位new允许我们在一个特定的,预先分配的内存地址上构造对象。
使用实例:
void* ptr = malloc(sizeof(Complex));
complex* ptr1 = new(ptr)Complex(2,3);
void* ptr2 = malloc(sizeof(Complex)*5);
void* ptr3 = new(ptr2)Complex[5]{{1,2},3};
free(ptr);
free(ptr2);
测试代码:
#include <iostream>
using namespace std;
class Complex
{
public:
int a;
int b;
public:
//默认构造函数
Complex():a(0),b(0){
cout<<"default Complex constructor"<<endl;
};
//有参数的构造函数
Complex(const int &i,const int &j=0):a(i),b(j){
cout<<"Complex constructor"<<endl;
}
//析构函数
~Complex(){
cout<<"Complex destructor"<<endl;
};
};
void* operator new(size_t size){
cout<<"operator new"<<endl;
return malloc(size);
}
void* operator new[](size_t size){
cout<<"operator new[]"<<endl;
return malloc(size);
}
void operator delete(void *p){
cout<<"operator delete"<<endl;
return free(p);
}
void operator delete[](void *p){
cout<<"operator delete[]"<<endl;
return free(p);
}
int main(){
//单个对象的定位new
void* p0 = malloc(sizeof(Complex));
cout<<"分配的内存地址:"<<p0<<endl;
Complex* p1 = new(p0)Complex(2,3);
cout<<"经过定位new操作后的内存地址:"<<p1<<endl;
cout<<endl<<endl;
//多个对象的定位new:
void* p2 = malloc(sizeof(Complex)*5);
cout<<"分配的内存地址:"<<p2<<endl;
Complex* p3 = new(p2)Complex[5]{{1,2},3};
cout<<"经过定位new操作后的内存地址:"<<p3<<endl;
free(p0);
free(p2);
system("pause");
return 0;
}
operator new/operator new[],operator delete/operator delete[]
operator new/operator new[]
operator new函数原型:
throwing(1) | void* operator new (std::size_t size); |
nothrow(2) | void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; |
placement(3) | void* operator new (std::size_t size, void* ptr) noexcept; |
(1)分配大小为size的字节,并适当对齐以表示该大小的对象,并返回指向该块第一个字节的非空指针。分配失败时(内存耗尽或者没有足够大的连续内存空间),它会抛出一个bad_alloc异常。
(2)与上面(1)的功能相同,除了失败时返回空指针而不是抛出异常。(默认定义调用第一个版本)
(3)返回ptr指针(不分配存储空间)
operator new[]函数原型:
throwing(1) | void* operator new[] (std::size_t size) ; |
nothrow(2) | void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; |
placement(3) | void* operator new[] (std::size_t size, void* ptr) noexcept; |
与operator new功能相同,区别在与它是数组形式的分配函数。
(1)默认定义调用operator new(size),功能与operator delete相同,只不过是数组形式。
(2)默认定义调用(1)
(3)返回ptr指针(不分配存储空间)
operator delete/operator delete[]
ordinary(1) | void operator delete (void* ptr) noexcept; |
nothrow (2) | void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; |
placement(3) | void operator delete (void* ptr, void* voidptr2) noexcept; |
(1)释放由ptr(如果非空)指向的内存块,释放先前通过operator new分配给它的存储空间,并使该指针位置无效。
(2)与(1)相同,默认定义调用第一个版本的operator delete
(3)什么也不做。
ordinary(1) | void operator delete[] (void* ptr) noexcept; |
nothrow (2) | void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; |
placement(3) | void operator delete[] (void* ptr, void* voidptr2) noexcept; |
(1)默认定义调用operator delete(ptr),功能与operator delete相同,只不过是数组形式。
(2)默认定义调用(1)
(3)什么也不做。
测试代码:
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
//默认构造函数
Complex():a(0),b(0){
cout<<"default Complex constructor"<<endl;
};
//有参数的构造函数
Complex(const int &i,const int &j=0):a(i),b(j){
cout<<"Complex constructor"<<endl;
}
//析构函数
~Complex(){
cout<<"Complex destructor"<<endl;
};
};
int main(){
//使用array new分配空间
void* ptr0 = operator new[](sizeof(Complex)*5);
//使用placement new构造对象,无法直接调用构造函数
new(ptr0) Complex[5]{1,2,3};
Complex* ptr1 = static_cast<Complex*>(ptr0);
//模拟delete[],先析构,后释放空间
for (size_t i = 0; i < 5; i++)
{
ptr1[i].~Complex();
}
operator delete[](ptr0);
system("pause");
return 0;
}
关于new与delete背后工作的图示
在候捷老师内存管理的课程中有图如下:展示了new表达式背后的工作,分配内存,类型转换,调用构造函数(只有编译器能直接调用ctor,构造函数),我们可以利用placement new间接调用ctor。以及在vc98中,operator new的实现细节,通过调用(CRT,C runtime libary),malloc来分配空间。
下图展示了delete表达式背后的工作,调用析构函数,调用operator delete(实际上通过调用free(),C函数)来释放空间。
NOTE:对于malloc分配的空间,可以通过placement new来调用构造函数,初始化对象。析构函数是可以直接被调用的
关于内存分配的CRT(C runtime Libary)
calloc/realloc/malloc/free
动态内存管理数 | 函数说明 |
---|---|
void* malloc (size_t size); | 分配一块大小为size字节的内存块,并返回一个指向该块开头的指针。新分配的内存块的内容未初始化,值不确定。如果size为零,则返回值取决于特定的库实现(它可以是NULL指针,也可以不是NULL指针),但是不应取消对返回的指针的引用。 |
void free (void* ptr); | 释放由malloc/calloc/realloc分配的内存块,使得这个内存块能再一次的被分配。如果ptr指向的是不是以上函数分配的内存块,那么会导致不确定的行为。ptr为空,则这个函数不做任何事。这个函数不会改变ptr的值,因此ptr仍然指向之前分配的内存。 |
void* calloc (size_t num,size_t size); | 为元素大小为size,含有num个元素的数组分配内存空间,并且把这个空间的所有位(bits)置为0。 |
void* realloc (void* ptr,size_t size); | 重新分配内存块。更改ptr指向的内存块的大小。该函数可以将内存块移动到新的位置(其地址由该函数返回)。即使将存储块移动到新位置,该内存块的内容也会保留到新旧大小中的较小者。如果新大小较大,则新分配部分的值不确定。如果ptr是空指针,则该函数的行为类似于malloc。 |
测试代码:
#include <iostream>
#include "stdlib.h"
using namespace std;
int main(){
int num = 10;
int *ptr = (int*)calloc(num,sizeof(int));
cout<<"ptr地址:"<<ptr<<endl;
for (size_t i = 0; i < num; i++)
{
cout<<ptr[i]<<",";
}
cout<<endl;
ptr = (int*)realloc((void *)ptr,num/2*sizeof(int));
cout<<"ptr地址:"<<ptr<<endl;
for (size_t i = 0; i < num/2; i++)
{
cout<<ptr[i]<<",";
}
cout<<endl;
ptr = (int*)realloc((void *)ptr,num*2*sizeof(int));
cout<<"ptr地址:"<<ptr<<endl;
for (size_t i = 0; i < num*2; i++)
{
cout<<ptr[i]<<",";
}
cout<<endl;
free(ptr);
system("pause");
return 0;
}
Reference
1.https://www.bilibili.com/video/BV1Kb411B7N8 C++内存管理,候捷,博览网课程
2.http://www.cplusplus.com/reference/cstdlib/ C++ reference