本文将会向您介绍C/C++内存管理方式,new与delete的原理与用法
C语言使用malloc/calloc/realloc/free函数来进行内存管理
int main()
{
int* p1 = (int*)malloc(sizeof(int) * 10);
//检查是否开辟失败
if (p1 == NULL)
{
perror("malloc fail");
return NULL;
}
//初始化
*p1 = 1;
//释放
free(p1);
p1 = NULL;
return 0;
}
如以上程序,在C语言中开辟空间时,我们不仅要手动sizeof计算大小还要对指针进行强转,另外还需要检查指针的合法性,还不能在开辟空间时进行初始化。
对于面向对象的C++,使用C的方式开辟空间不仅仅是不太好用,还满足不了一些地方的需求
如:以下是一个Stack类
typedef char DataType;
class Stack
{
public:
Stack(size_t capacity = 4)
{
cout << "Stack" << endl;
_capacity = capacity;
_array = new DataType[capacity];
//_array = new char[0x7fffffff];
_size = 0;
}
void Push(DataType data)
{
_array[_size] = data;
_size++;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
public:
//内置类型
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack* p1 = (Stack*)malloc(sizeof(Stack) * 10);
return 0;
}
malloc不会调用类的构造函数,也无法正确地初始化类的成员变量
因此C++使用new与delete操作符进行动态内存管理
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
new的原理
自定义类型
1、调用operator new函数申请空间在申请的空间上执行构造函数,完成对象的构造
delete的原理
1、在空间上执行析构函数,完成对象中资源的清理工作调用
2、operator delete函数释放对象的空间
new T[N]的原理
1、调用operator new[]函数,在operator new[]中实际调operator new函数完成N个对象空间的申请
2、在申请的空间上执行N次构造函数
delete[]的原理
1、在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2、调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
格式(仅供参考):
T* p = new T
delete p
//初始化
T* p = new T()
delete p
//多个对象
T* p = new T[ ]
delete[] p
//多个对象+初始化
T* p = new T[]{}
delete[] p
new:操作符
T:类型
():初始化
[]:对象个数
{}:初始化
int main()
{
//自定义类型,开空间,调用构造函数初始化
A* p2 = new A;
A* p3 = new A(3);
//自定义类型。调用析构函数 + 释放空间
delete p2;
delete p3;
}
class A
{
public:
A(int a = 0)
:_a(a)
{}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
int main()
{
A* p4 = new A[10];
delete[] p4;
A aa1(1);
A aa2(2);
A* p5 = new A[10]{ aa1,aa2 };
delete[] p5;
//用匿名对象初始化
A* p6 = new A[10]{ A(1),A(2) };
delete[] p6;
//隐式类型转换
A* p7 = new A[10]{ 1,2 };
delete[] p7;
}
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
void func()
{
Stack st;
char *p1 = new char[0x7fffffff];
cout << (void*)p1 << endl;
}
int main()
{
try
{
func();
cout << "hello" << endl;
}
//捕获异常
catch (const exception& e)
{
cout << e.what() << endl;
}
}
若是在try块中抛出了异常,那么异常后的代码将不会继续执行
程序会立刻跳转至catch块
operator new 与 operator delete 时系统提供的全局函数,new在底层调用operator new 全局函数来申请空间,delete在底层用operator delete全局函数来释放空间
int main()
{
int* p1 = new int;
delete p1;
}
Stack* pst1 = (Stack*)operator new(sizeof(Stack));
operator delete(pst1);
另外我们可以通过反汇编观察到operator new、operator delete的底层。其实operator new 与 operator delete就是封装的malloc与free
通过以上内容我们可以总结出:
new :operator new(封装的malloc) + 构造函数(内置类型不会调用)
delete:operator delete(封装的free) + 调用析构函数(内置类型不会调用)
我们来看以下程序
class A
{
public:
A(int a = 0)
:_a(a)
{}
~A()
{
cout << "~A" << endl;
}
private:
int _a;
};
内置类型
int* p1 = new int[10];
free(p1);
delete p1;
delete[] p1;
当类型为内置类型的时候,以上三种释放空间的方式都不会报错,原因是delete在底层调用operator delete(封装的free)和调用析构函数(内置类型不会调用)
A* pst2 = new A[10];
//delete pst2;
//free(pst2);
delete[] pst2;
当类型为自定义类型,我们就必须要匹配进行使用了
malloc/free 和 new/delete的区别:
1.malloc和free是函数,new和delete是操作符
2.malloc申请的空间不会初始化,new可以初始化
3.malloc申请空间时,需要手动计算空间大小并传递,new仅需要在其后跟上空间的类型即可,如果十多个对象,[ ] 中指定对象个数即可
4.malloc的返回值为ivoid* 在使用时必须强转,new不需要,因为new后跟的时空间的类型
5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6.申请自定义类型对象时,malloc/free只会开辟与释放空间,不会调用构造与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
我们先看以下程序
int globalVar = 1;
static int staticGlobalVar = 1;
int main()
{
int num[] = {1, 2, 3, 4};
char char2[] = "abcd";
const char* pchar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?C
staticGlobalVar在哪里?C
staticVar在哪里?C
localVar在哪里?A
num1 在哪里?A
char2在哪里?A
*char2在哪里?A
pChar3在哪里 ? A
*pChar3在哪里?D
ptr1在哪里?A
*ptr1在哪里?B
注意:char2是数组名,是数组首元素的地址,存储在栈上,
*char2,解引用char2得到数组首元素a,数组在栈上
char char2[] = "abcd";
这句代码的含义是用”abcd“这个常量字符串初始化char2
常量字符串属于代码段
而pchar3是一个指向常量字符串的指针,存储在栈上
指针存储常量字符串的地址,解引用指针得到常量字符串
今天就到这里啦,如果本文存在疏漏或错误的地方,还请您能够指出。