C动态内存管理方式
1.堆上:
C语言中使用malloc /calloc/ realloc/ free进行动态内存管理,但对上的内存需要用户自己来管理,动态malloc /calloc/ realloc的空间必须free掉,否则会造成内存泄漏
2.栈上
使用_alloca在栈上动态开辟内存,栈上开辟的内存由编译器自动维护,不需要用户显示释放C
C++动态内存管理方式
1.new操作符
new delete操作符是C++用来动态申请内存的关键
malloc/free和new/delete的区别与联系
- 它们都是动态管理内存的入口
- malloc/free是C/C++标准库的函数,new/delete是C++操作符
- malloc/free只是动态分配内存空间/分配空间,而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)
- malloc/free需要手动计算类型的大小,返回对应类型的指针void *,new/delete/可自己计算类型的大小,返回对应类型的指针
2,操作符new
操作符new是一个函数
void * operator new(size_t size);//allcoate an object
void operator delete(size_t size);//free an object
void * operator new[](size_t size);//allocate an array
void operator delete[](size_t size);//free an array
前面两个均是C++标准函数
总结:
- operator new/operator delete operator new[]/operator delete[] 和 malloc/free用法一样
- 他们只负责分配空间/释放空间,不会调用对象构造函数/析构函数来初始化/清理对象
- 实际operator new和operator delete只是malloc和free的一层封装
3.new和delete背后机制
下面我们用一个实例来来解释
class A
{
public:
A(int v)
:var(v)
{
fopen_s(&file,,);
}
~A()
{
fclose(file);
}
private:
int var;
FILE *file;
}
很简单,类A中有两个私有成员,有一个构造函数和一个析构函数,析构函数初始化私有变量var以及打开一个文件。析构函数负责关闭文件
我们使用
class A *pA=new A(10);
来创建一个类的对象,返回其指针pA,如下图所示new背后完成的工作
简单总结一下:
- 首先需要调用上面提到的operator new 标准库函数,传入的参数为A的大小,这里为8个字节,这样函数返回的是分配内存的起始地址,这里假设是0x007da290
- 上面分配的内存是未初始化的,也是未类型化的,第二步就在这一块原始的内存上对类型进行类对象初始化,调用的是相应的构造函数,这里是调用A:A(10);这个函数,从图中也可以看到对这块申请的内存空间进行了初始化,var=10;file指向打开的文件。最后一步就是返回新分配并构造好的对象的指针,这里pA就指向0x007da290这块内存,pA的类型就为A对象的指针
- 反汇编
delete解析
delete就做了两件事情:
- 调用pA指向对象的析构函数,对打开的文件进行关闭
- 通过上面的提到的标准库函数operator delete来释放该对象的内存,传入的参数为pA的值。也就是0x007da290
总结:
如何申请和释放一个数组
- 我们经常要用到动态分配一个数组,也许是这样的:
string *psa = new string[10]; //array of 10 empty strings
int *pia = new int[10]; //array of 10 uninitialized ints
- 上面在申请一个数组时都用到了 new [] 这个表达式来完成,按照我们上面讲到的 new 和 delete 知识,第一个数组是 string 类型,分配了保存对象的内存空间之后,将调用 string 类型的默认构造函数依次初始化数组中每个元素;第二个是申请具有内置类型的数组,分配了存储 10 个 int 对象的内存空间,但并没有初始化。
- 如果我们想释放空间了,可以用下面两条语句:
delete [] psa;
delete [] pia;
- 都用到delete[]表达式,注意这地方的[]一般情况下不能漏掉,第一个对10个string类对象分别调用析构函数,然后在释放掉为对象分配的所有内存空间;第二个因为是内置类型不存在析构函数,直接释放为10个int型分配的所有内存空间,这里对于第一种情况就有一个问题了,我们如何知道psa指向对象的数组的大小?怎么知道调用几次析构函数?
- 这个问题直接导致我们需要在new[]一个对象数组时,需要保存数组的维度,C++的做法是分配数组空间时多分配了4个字节的大小,在delete[]时就可以取出这个保存的数,就知道了需要调用析构函数多少次
- 还是用图来说明比较清楚,我们定义了一个类A,但不具体描述类的内容,这个类中有显示的构造函数,析构函数等,那么当我们调用class A*pAa=new A[3];需要做的事情如下:
总结:
这样的话,释放就很简单了:
注意:
- 调用析构函数的次数是从数组对象指针前面的 4 个字节中取出;
- 传入 operator delete[] 函数的参数不是数组对象的指针 pAa,而是 pAa 的值减 4。
总结: