我们在C语言中了解到可以在栈区动态开辟空间,并且用完要进行释放,防止内存泄漏。
引入
C++中也有可以进行动态开辟空间和释放空间的操作符new 、delete,虽然C++中也可以用malloc、calloc、realloc、free函数,但是C++中引入了类,而类中又有构造函数和析构函数,在实例化对象时会默认调用。如果你想在堆上开辟类类型的空间继续malloc的话,那么该空间的内容就不会自动调用构造函数,而free也不会自动调用析构函数,因为你创建的类指针变量存的是这块空间的地址,属于内置类型,所以程序结束时也不会自动调用析构函数。
对内置类型的内存管理
int main() { // 动态申请一个int类型的空间,不初始化 int* ptr1 = new int; // 动态申请一个int类型的空间并初始化为10 int* ptr2 = new int(10); // 动态申请3个int类型的空间,并初始化为1 2 3 int* ptr3 = new int[3] {1, 2, 3}; delete ptr1;//释放单个空间 delete ptr2; delete[] ptr3;//释放连续空间 return 0; }
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[]。其实此时对于内置类型而言new与delete相较于malloc与free实现的功能其实是一样的。
对自定义类型的内存管理
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; };
int main() { A* p1 = (A*)malloc(sizeof(A)); //A* p2 = new A(1); free(p1); //delete p2; return 0; }
运行结果:无
int main() { //A* p1 = (A*)malloc(sizeof(A)); A* p2 = new A(1); //free(p1); delete p2; return 0; }
运行结果:new空间时自动调用构造函数,delete空间时会先调用析构函数
int main() { A* p2 = new A[5]; delete[] p2; return 0; }
运行结果:会在new的时候调用5次构造函数,在delete之前调用5次析构函数
operator new与operator delete函数
其实操作符new、delete也不是单纯的直接实现的,其实,在它们内部还调用着对应的函数operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new
其实operator new也是通过调用malloc函数实现的,在堆区开辟空间,但是在malloc的基础之上稍稍加了修饰,malloc调用失败会返回NULL,而operator new在此基础上加了判断条件:
while ((p = malloc(size)) == 0)//C++中NULL就是0
此时如果为空的话,就会抛异常报错。
operator delete
其实像operator new一样,operator delete是通过free来释放空间的。
new和delete的实现原理
其实new和delete操作符也不是仅仅就调用了operator new和operator delete那么简单,实际上还调用了类的构造函数,而delete还调用了类的析构函数(delete是先调用析构函数,再调用operator delete函数释放创建的空间)
- new A[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
象空间的申请
2. 在申请的空间上执行N次构造函数
- delete[]的原理
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
放空间
未匹配报错
class A
{
public:
A(int a = 0)
: _a(a)
{
//cout << "A():" << this << endl;
}
~A()
{
//cout << "~A():" << this << endl;
//_a = 0;
}
private:
int _a;
};
int main()
{
A* p = new A[5];
delete p;
return 0;
}
此时delete未与new[]匹配,其实应该delete[]p来释放空间的,此时其实就是未匹配的原因报错的。
实际上当你执行:
A* p = new A[5];
并不是开辟5个A类型大小的空间,实际上会多开辟四个字节来存放你要开辟的空间的数字,也就是5,存着的目的也就是通过指针偏移得到这四个字节的数据,从而调用多少次析构函数,所以必须要用delete[]p来释放才行(实际上[]里接收的就是前面四字节空间存的5)。但是调用operator delete时,指针会偏移指向最前面,释放所有的空间,也就包括前面的四个字节。
所以 用delete p来释放空间就肯定会报错,不仅仅不会调用5次析构函数,而且最主要还是不会将前面四字节空间释放,所以就:
其实我演示的是在x86环境下的,所以就多开辟四字节空间存放数据,试试看在x64环境下,貌似是开辟8字节空间存放数据。
这里A占4个字节,-1就是偏移四字节,-2就是偏移8字节。
其实你不写析构函数的话,编译器也不会报错
class A
{
public:
A(int a=0)
: _a(a)
{}
private:
int _a;
};
int main()
{
A* p = new A[5];
delete p;
return 0;
}
因为VS编译器进行优化了,没写析构函数,编译器默认生成,但是默认生成也不会执行什么命令,此时编译器就会进行优化,所以就不会再多开辟字节去存放开辟空间的数字了,所以调用operator delete时就是正确的,不会报错,但是可能会内存泄漏(由于没有调足够的析构函数)
欢迎各位莅临指导!!!