C++内存管理
文章目录
C++的内存区域分布情况
- 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下) - 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段–存储全局数据和静态数据。
- 代码段–可执行的代码和只读常量
C语言和C++内存管理的区别
C语言:malloc/realloc/calloc/free
C++: new/delete/new[]/delete[]
C语言的内存管理在C++中依然可以继续使用,但有些场景真的是不尽人意,我们可以选用new和delete来进行内存管理
C++申请内置维护类型空间:
int main() {
int* p_int = new int;//从堆区申请一个int 的空间,将地址赋值给指针p_int
*p_int = 20;
delete p_int;//从堆区释放内存
int* p_arr = new int[10];//从堆区申请10个int 的连续空间,将首地址地址赋值给指针p_int
delete[] p_arr;//从堆区释放int数组的内存
return 0;
}
C++申请自定义类型的空间
class A {
private:
int _a;
public:
A()
{
cout << "构造函数" <<endl;
}
~A() {
cout << "析构函数" << endl;
}
};
int main() {
A* p_A = new A;
delete p_A;
return 0;
}
运行结果:
两个全局函数operator new和operator delete函数(重点)
C++里的new和delete是运算符重载的,他们的本质是调用operator new和operator delete函数
operator new函数最终是调用的malloc来开辟的内存,其实C++就是在C语言功能上进行的完善和修改进一步封装
同理delete最终调用的也是free。
下边是源码:
//operator new
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
//operator delete
operator delete: 该函数最终是通过free来释放空间的
*/
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 delete 最终是通过free来释放空间的。
两个全局函数工作的原理
对单个对象的申请都大同小异,但是对于连续空间比如数组的申请就是我们这次重点要讨论的了。
new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 在申请的空间上执行N次构造函数
delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
使用里的一些问题
但是我们不禁会产生一些疑问:
比如
1、 既然C++是兼容C的那么malloc和delete可以混合使用嘛?
2、delete[] 会在结束时候对对象进行析构,那么它是怎么知道要进行多少次析构呢?
3、如果不能以上问题中,两种风格混着使用会引起什么问题呢?
我们将对以上问题 一一详细解答
首先来看第一个:
malloc和delete可以混合使用嘛?
答案是不可以,因为delete会进行析构,而malloc时候都不会调用构造函数来初始化,谈何析构而言呢?
delete[] 会在结束时候对对象进行析构,那么它是怎么知道要进行多少次析构呢?
来看以下代码:
class A {
private:
int _a;
public:
A()
{
cout << "构造函数" <<endl;
}
~A() {
cout << "析构函数" << endl;
}
};
int main() {
A* a = new A[5];
delete[] a;
return 0;
}
我们在vs的调试界面对内存进行观察:
我们看右边内存里的东西。我们可以观察到下边框起来的成功开辟出来了所需要的空间,但是在这之前注意下上边框里的内容,它里边存放了个数字5。大家可以先合理猜测下。
我们这样用delete[]可以正常释放掉所有空间,看下图。我们可以明确 上边那个5确实是我们开辟出来的
这里我来提出一个问题,来提醒下大家,我们这里在调试界面是直接输入a来定位内存在0x00FCB524也就是说a指向这个地址,这个地址按道理来讲是首地址,但是a之前还有4个字节来存放了一个5,这里我们合理猜测他就是用来存放析构次数的功能。我们知道C语言是不支持内存的分段式放的,但是a指向的不是首地址的话,那就是delete[]进行了特殊处理,那么我们用C语言的free来释放会引起什么问题呢?看下图:
显然程序崩溃了。这样上边的第三个问题也就回答了
两种风格绝对不可以乱用,一定要配套使用,因为他们实现原理是不同的,尤其是自定义类型的动态开辟,切记不要乱用。