C++入门之内存管理
文章目录
1. C/C++中程序在内存中的区域划分
- 栈:又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的(从高地址向低地址增长)
- 堆:用于程序运行时动态内存分配,堆是可以上增长的(从低地址向高地址增长)
- 数据段(静态区):存储全局数据和静态数据。
- 代码段(常量区):可执行的代码/只读常量
- globalVar 和 staticGlobalVar 是全局变量,staticVar 被 static修饰,是静态变量,全局变量和静态变量位于数据段(静态区)
- localVar num 等等都是局部变量,位于栈上
- “abcd” 为常量字符串,是不可修改的,只读,位于代码段(常量区)
- malloc动态申请的空间,位于堆上
2. C语言中动态内存管理方式:malloc/calloc/realloc/free
简单回顾以下C语言中动态内存管理的方式
#include <iostream>
using namespace std;
int main()
{
int* p1 = (int*)malloc(sizeof(int)); //不会初始化
free(p1);
int* p2 = (int*)calloc(4,sizeof(int) );//会将数据初始化为0
int* p3 = (int*)realloc(p3, sizeof(int) * 10);//重新分配内存
free(p3);
return 0;
}
区别:malloc不会初始化空间,而calloc会将空间初始化为0,而realloc则是重新分配空间
3. C++中动态内存管理方式:new/delete
C语言中的方式依旧可以使用,C++中提出了新的方式new和delete操作符进行动态内存管理
- 对于内置类型
#include <iostream>
using namespace std;
int main()
{
int* p1 = new int;//开辟一个int的空间
int* p2 = new int(10);//开辟一共int的空间并初始化为10
int* p3 = new int[10] {1,2,3,4};//开辟10个int的空间,且初始化前4个,剩下的则初始化为0
delete p1; //释放一个int的空间
delete p2;
delete[] p3;//释放多个int的空间
return 0;
}
- 对于自定义类型
#include <iostream>
using namespace std;
class A
{
public:
A(int x = 10)
:_a(x)
{
cout << "A(int x = 10)" << endl;
}
A(int x, int y)
:_a(y)
{
cout << "A(int x, int y)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A; //调用默认构造
A* p2 = new A(1); //传值调用默认构造
A* p3 = new A(1,2); //调用两个参数的构造
A* p4 = new A[10]{ 1,2,3,{4,5} };//可以调用默认构造和普通构造混着来,剩下的则调用默认构造,共调用10次构造
//调用析构
delete p1;
delete p2;
delete p3;
delete[] p4; //调用10次析构
return 0;
}
与C语言中的最大区别:
malloc calloc realloc和new delete在处理内置类型时,几乎是一样的,但是在处理自定义类型时,newdelete除了开辟/释放空间,还会调用类的默认构造和析构函数
- new T [n]
#include <iostream>
using namespace std;
class A
{
public:
A(int x = 10)
:_a(x)
{
cout << "A(int x = 10)" << endl;
}
private:
int _a;
};
int main()
{
A* p2 = new A[10]; //可能会多开辟4个字节的空间 44 or 40
delete[] p2;
return 0;
}
在上述代码中,有析构函数时,可能会多开辟4个字节的空间,多开辟的4个字节空间用来存储对象的个数,方便在delete[]时,知道有几个对象,需要调用几次析构函数
- new 和 delet 应当配对使用,否则结果是不确定
#include <iostream>
using namespace std;
class A
{
public:
A(int x = 10)
:_a(x)
{
cout << "A(int x = 10)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
int* p1 = new int[10]; //40
free(p1);//没问题,但是不建议
A* p2 = new A[10]; //44 or 40
free(p2);//程序奔溃
return 0;
}
由于类中存在析构函数,可能会多开辟4个字节的空间来存储对象的个数,使用delete[ ]时,会从多开辟的位置开始释放,而free不会,free的位置错误,导致程序奔溃
4. operator new与operator delete函数
newdelete是动态内存申请和释放的操作符,而operator new与operator delete是两个全局函数,operator new与operator delete不是重载的newdelete
- new在底层是通过operator new这个全局函数来申请空间的
- delete在底层是通过operator delete这个全局函数来释放空间的
//operator new operator delete的源码
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
简单来说:
- operator new实际上是通过malloc来申请空间的,不过malloc失败时是返回的是空指针,且需要检查是否申请空间成功,而operator new失败后则会抛出异常,比返回空指针更直观,operator new相当于是malloc的封装 + 失败抛出异常
- operator delete实际上是通过free来释放空间的,是与 operator new完成一个配对
5. new和delete的实现原理
new
- 先调用operator new函数(调用malloc)来申请空间,失败则抛出异常
- 再调用类的构造函数,完成对象的构造
delete
- 先调用类的析构函数,完成类中对象的资源清理
- 再调用operator delete函数(调用free)来释放空间
new T[N]
- 先调用operator new[] 函数(实际调用operator new)来申请空间,完成对N个对象的空间申请(一次完成)
- 再在申请的空间上调用N次构造函数
delete[]
- 先调用N次析构函数,完成N个对象的资源清理
- 再调用operator delete[] 函数(实际调用operator delete)来释放空间
6. malloc/free和new/delete的区别
-
- malloc/free是函数,而new/delete是操作符
-
- malloc的返回值为void*,需要强转使用,而new不需要,new后跟的空间的类型
-
- malloc/free开辟的空间不会初始化,而new/delete可以初始化
-
- malloc/free开辟空间时需要手动计算大小, new只需开辟在[]指定数量
-
- malloc开辟的空间失败时,返回一个空指针,因此使用前需要判断是否为空,而new失败后会抛出异常,只需捕获异常
-
- 在处理自定义类型时, malloc/free只会开辟/释放空间,而
new在开辟空间后,还会调用构造和析构, delete在释放空间前,还会调用析构
- 在处理自定义类型时, malloc/free只会开辟/释放空间,而