目录
这里是oldking呐呐,感谢阅读口牙!先赞后看,养成习惯!
个人主页:oldking呐呐
专栏主页:深入CPP语法口牙
10 C/C++内存管理
10.1 内存分布
- CPP的内存划分和C保持一致
- 栈 – 用于非静态局部变量/函数参数/返回值,栈是向下增长的
- 内存映射段 – 高效的I/O映射方式,用于装载一个共享的动态内存库,用户可以使用系统接口创建共享内存,做进程间通信(暂时简单了解即可)
- 堆 – 用于程序运行时的动态内存分配,堆可以向上增长
- 数据段 – 存储全局数据和静态数据
- 代码段 – 可执行的代码/只读常量
10.2 C的动态内存管理
malloc()
,calloc()
,realloc()
用于申请空间(调整空间)free()
用于释放空间
10.3 C++的内存管理
-
CPP可以继续使用C的内存管理方式,但CPP引入了更加方便的关键字用于内存管理,即
new
和delete
-
基础玩法:
int main()
{
int* pa1 = new int; //内置类型声明+定义
int* pa2 = new int(1); //内置类型声明+定义+初始化
delete pa1; //内置类型销毁
delete pa2; //内置类型销毁
int* pb1 = new int[10]; //数组声明+定义
int* pb2 = new int[10] {0}; //数组声明+定义+初始化(全为0)
int* pb3 = new int[10] {1, 2, 3, 4, 5, 6}; //数组声明+定义+初始化(前六个分别为1,2,3,4,5,6,后4个默认全是0)
delete[] pb1; //数组销毁
delete[] pb2; //数组销毁
delete[] pb3; //数组销毁
return 0;
}
- 进阶玩法(完全区别于C语言的玩法):
class A
{
public:
A(int x = 1)
:_x(x)
{
cout << "A(int x = 1)->" << _x << endl;
}
A(const A& a)
:_x(a._x)
{
cout << "A(const A& a)->" << _x << endl;
}
~A()
{
_x = 0;
cout << "~A()" << endl;
}
private:
int _x = 1;
};
int main()
{
A* ca1 = new A; //自定义对象的声明+定义+使用缺省值初始化
A* ca2 = new A(3); //自定义对象的声明+定义+使用用户的手动传值初始化
delete ca1; //自定义类型的销毁
delete ca2; //自定义类型的销毁
cout << endl; //分割
cout << "-------------------------" << endl; //分割
cout << endl; //分割
A* ca3 = new A[3]; //自定义类型数组的声明+定义+使用缺省值初始化
A* ca4 = new A[3]{ 0 }; //自定义类型数组的声明+定义+第一个数使用用户传值初始化,其他数使用缺省值进行初始化
A* ca5 = new A[3]{ 5, 6 }; //自定义类型数组的声明+定义+第一/二个数使用用户传值初始化,其他数使用缺省值初始化
delete[] ca3; //自定义类型数组的销毁
delete[] ca4; //自定义类型数组的销毁
delete[] ca5; //自定义类型数组的销毁
return 0;
}
- 区别于传统C的写法,
new
和delete
最大的优势在于它会自动调用构造函数和析构函数
- 如果是多参数的话:
class A
{
public:
A(int x = 1, int y = 2)
:_x(x)
,_y(y)
{
cout << "A(int x = 1)->" << _x << " " << _y << endl;
}
A(const A& a)
:_x(a._x)
,_y(a._y)
{
cout << "A(const A& a)->" << _x << " " << _y << endl;
}
~A()
{
_x = 0;
cout << "~A()" << endl;
}
private:
int _x = 1;
int _y = 2;
};
int main()
{
A* ca1 = new A; //用缺省值初始化
A* ca2 = new A(3, 4); //用用户传值初始化
A* ca3 = new A[2]{ A(5, 6), A(7, 8)}; //用临时变量初始化
A* ca4 = new A[2]{ {10, 11},{12, 13} }; //用隐式类型转换初始化
delete ca1; //销毁
delete ca2;
delete[] ca3;
delete[] ca4;
return 0;
}
- 不难看出,这里是有进行优化的,编译器把临时对象省略掉了(VS2022-debug-x64)
10.4 new
失败的检测
- 这一部分的内容暂时知道这么回事就可以了,涵盖了异常处理的一些内容
int main()
{
try //尝试
{
void* ca2 = new char[1024 * 1024 * 1024];
cout << ca2 << endl;
void* ca3 = new char[1024 * 1024 * 1024];
cout << ca3 << endl;
}
catch (const exception& e) //尝试失败就会调用catch一个叫exception的东西,exception是一个异常类型
{
cout << e.what() << endl; //打印发生了什么
}
return 0;
}
-
在32位下,程序所开辟的空间不够,所以报出开空间错失败的报错信息
-
不仅仅
new
可以抛异常,函数也可以抛异常
void func1()
{
void* ca2 = new char[1024 * 1024 * 1024];
cout << ca2 << endl;
void* ca3 = new char[1024 * 1024 * 1024];
cout << ca3 << endl;
}
int main()
{
try
{
func1();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
new
一般不会失败,不过还是要检测的
10.5 operator new
与operator delete
函数
-
operator new
和operator delete
是系统写的两个全局函数 -
简单来说,为了让
new
能套用进C++异常抛出的那套逻辑里,设计者把抛出逻辑直接写进operator new
里,我们可以直接看以下底层代码
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void *p;
while ((p = malloc(size)) == 0) //底层其实还是malloc
if (_callnewh(size) == 0)
{
static const std::bad_alloc nomem; //如果内存申请失败,就会进到这个if里,并抛出错误异常
_RAISE(nomem);
}
return (p);
}
-
简单来说
operator new
=malloc()
+ 判断与抛出错误异常 -
而
operator delete
也是一样
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 delete
里,使用到了一个叫做_free_dbg
的东西,其实这个东西就是free()
的宏定义
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
-
事实上,
operator new
与operator delete
其实也并不是new
或者delete
的定义,前者只是后者的一部分而已 -
现在我们整一个自定义类型,用
new
申请空间到堆上,此时打开反汇编
006B2704 push 8
006B2706 call operator new (06B1140h)
006B270B add esp,4
006B270E mov dword ptr [ebp-0ECh],eax
006B2714 mov dword ptr [ebp-4],0
006B271B cmp dword ptr [ebp-0ECh],0
006B2722 je __$EncStackInitStart+61h (06B273Bh)
006B2724 push 2
006B2726 push 1
006B2728 mov ecx,dword ptr [ebp-0ECh]
006B272E call A::A (06B1366h)
006B2733 mov dword ptr [ebp-0F4h],eax
006B2739 jmp __$EncStackInitStart+6Bh (06B2745h)
006B273B mov dword ptr [ebp-0F4h],0
006B2745 mov eax,dword ptr [ebp-0F4h]
006B274B mov dword ptr [ebp-0E0h],eax
006B2751 mov dword ptr [ebp-4],0FFFFFFFFh
006B2758 mov ecx,dword ptr [ebp-0E0h]
006B275E mov dword ptr [a],ecx
-
不难发现,这条语句一定做了两件事情
006B2706 call operator new (06B1140h)
这条语句调用了operator new
006B272E call A::A (06B1366h)
这条语句调用了构造函数
-
所以我们可以理解为,设计者为了让
malloc()
能套用进CPP的体系中,将malloc()
升级加强成为了new
-
一个给自定义类型开辟空间的
new
一定干了3件事- 用
malloc()
开了空间 - 检测异常,如果有就抛出异常
- 调用自定义对象的构造函数
- 用
-
现在再来看一看
delete
00AB6721 mov eax,dword ptr [a]
00AB6724 mov dword ptr [ebp-0F8h],eax
00AB672A cmp dword ptr [ebp-0F8h],0
00AB6731 je __$EncStackInitStart+0AEh (0AB6748h)
00AB6733 push 1
00AB6735 mov ecx,dword ptr [ebp-0F8h]
00AB673B call A::`scalar deleting destructor' (0AB14ECh)
00AB6740 mov dword ptr [ebp-100h],eax
00AB6746 jmp __$EncStackInitStart+0B8h (0AB6752h)
00AB6748 mov dword ptr [ebp-100h],0
- 接着转到
00AB673B call A::`scalar deleting destructor'
里
00AB2410 push ebp
00AB2411 mov ebp,esp
00AB2413 sub esp,0CCh
00AB2419 push ebx
00AB241A push esi
00AB241B push edi
00AB241C push ecx
00AB241D lea edi,[ebp-0Ch]
00AB2420 mov ecx,3
00AB2425 mov eax,0CCCCCCCCh
00AB242A rep stos dword ptr es:[edi]
00AB242C pop ecx
00AB242D mov dword ptr [this],ecx
00AB2430 mov ecx,dword ptr [this]
00AB2433 call A::~A (0AB14F1h)
00AB2438 mov eax,dword ptr [ebp+8]
00AB243B and eax,1
00AB243E je __$EncStackInitStart+31h (0AB244Eh)
00AB2440 push 8
00AB2442 mov eax,dword ptr [this]
00AB2445 push eax
00AB2446 call operator delete (0AB109Bh)
00AB244B add esp,8
00AB244E mov eax,dword ptr [this]
00AB2451 pop edi
00AB2452 pop esi
00AB2453 pop ebx
00AB2454 add esp,0CCh
00AB245A cmp ebp,esp
00AB245C call __RTC_CheckEsp (0AB12FDh)
00AB2461 mov esp,ebp
00AB2463 pop ebp
00AB2464 ret 4
00AB2467 int 3
00AB2468 int 3
00AB2469 int 3
00AB246A int 3
00AB246B int 3
00AB246C int 3
00AB246D int 3
00AB246E int 3
00AB246F int 3
-
不难看出,这里有两个语句
00AB2433 call A::~A (0AB14F1h)
– 调用析构00AB2446 call operator delete (0AB109Bh)
– 调用operator delete
-
其实也就是说
delete
就是free()
的升级版- 调用析构
- 调用
free()
-
而
new[]
和delete[]
的底层调用了叫operator new[]
和operator delete[]
,但operator new[]
和operator delete[]
的底层还是new
和delete
这俩,其实就是套娃
10.5 new
与malloc()
的区别,delete
与free()
的区别
-
new
与malloc()
的区别,主要还是体现在CPP引入的一些新内容上,为了适配CPP引入的新内容,前者是后者的升级版new
不需要手动设定开辟空间的大小,而malloc()
需要手动设定空间开辟的大小new
能完成异常抛出,而malloc()
不行,malloc()
需要手动判空new
能调用构造函数进行初始化,但malloc()
不行malloc()
返回的值为void*
,需要强转成需要的类型,而new
不需要强转,他自动返回相应类型的指针new
是操作符,malloc()
是函数
-
同理,
delete
也相当于是free()
的升级版delete
会调用析构函数释放资源,但free()
就只是负责把空间归还delete
是操作符,free()
是函数
10.6 定位new
表达式
- 先看一个例子
class A
{
public:
A(int x = 1, int y = 2)//默认构造
:_x(x)
,_y(y)
{
cout << "A(int x = 1, int y = 2)->" << _x << " " << _y << endl;
}
A(const A& ra)//拷贝构造
:_x(ra._x)
,_y(ra._y)
{
cout << "A(const A& ra)->" << _x << " " << _y << endl;
}
~A()//析构
{
cout << "~A()" << endl;
}
private:
int _x;
int _y;
};
int main()
{
A* a1 = new A(1, 2); //声明+定义+初始化a1
delete a1; //调用析构+释放内存
(上面和下面完全没有区别,下面的情况完全就是脱裤子放屁,多此一举)
//------------------------------------------------
A* a2 = (A*)operator new(sizeof(A)); //声明+定义a1
new(a2)A(1, 2); //初始化a1(其实这里就是在调用构造函数)
a2->~A(); //调用析构函数
operator delete(a2); //释放内存
return 0;
}
-
可以看到,
operator new
和operator delete
这俩函数都可以手动调用,包括~A()
,也是可以手动调用的,但构造函数没办法手动调用,就是能靠new(a2)A(1, 2)
这个语句来调用,new
后面括号里放对象名,紧接着对象类型,在后面括号里放传参的内容 -
大部分情况下,正常使用
new
和delete
就能完成任务了,少部分情况例如在内存池中就必须使用operator new
和operator delete
内存池是向堆申请的一部分空间,用来专门存放经常需要申请和释放内存的数据,内存池与堆是独立开的,所以如果想从内存池中申请空间,就不能用在堆申请空间的那套模式,于是就有了以上模式,相当于手动模拟申请空间,只不过实际上申请的是内存池的空间