今天我们讲的内存是指的我们机器运行时与操作系统交互的内存(不是磁盘),这些内存又会被划分为了几个区域,这些区域的用法是不同的;接下来就让我们更多的了解一下内存:
内存分布:
我整理了思路,对照学习画出了上图并加以解释;通过上面的图我们可以大致的知道我们的内存空间被怎划分,它们又各自储存和担任着什么角色;我们了解了这些之后我们遇到问题才好从根源入手,更好更高效的解决问题;
我们知道了内存的大概划分后我们应该
如何管理内存呢?
在C语言中我们使用malloc,calloc,realloc这些函数来动态开辟空间,使用free来释放我们开辟的空间,利用这些向堆区申请空间并使用;
为了防止我们的遗忘,我们再来回顾一下这些代码:
malloc:
void *malloc( size_t size );这是它的函数实现,我们传参时需要传入我们的字节数,通过字节数来在堆区上开辟空间,之后再将malloc返回值强转为我们需要的类型然后赋值给我们相应的指针;
calloc:
void *calloc( size_t num, size_t size );这是它的函数实现,我们传参需要传入我们的赋值,还有要开辟的空间的字节数;我们的函数会帮我们开辟相应字节数的空间,然后把这些空间赋值为我们传参的值;最后和malloc一样转换类型赋值给指针;
realloc:
void *realloc( void *memblock, size_t size );这是它的函数实现,我们传入我们的需要扩容的原空间的地址和我们新空间的大小;当我们原空间所处位置足够我们增容时我们会在原空间后方扩容成我们传入空间的大小,如果我们原空间所处位置空间不够会在堆区的其他位置找出一片可以为我们开辟出新空间大小的位置,开辟出新空间的大小;
说了C语言的内存管理方式现在正是说一说我们c++该如何管理内存,我们c++是继承了c的所以我们可以用上面的代码来管理空间,但是我们上面的代码都只能开辟我们内置类型的空间,我们的自定义类型在使用上面的代码开辟时无法被调用我们之前学的构造和析构函数,这样的话就会影响我们c++的功能,于是c++就发明了新的内存管理的操作符new和delete;它们既可以开辟空间也可以调用我们的构造函数和析构函数;
new,delete开辟内置类型空间,使用new[ ],delete[ ]开辟连续的空间;
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
通过上图,我们也可以大致明白使用方式;
new,delet操作自定义类型的空间:
A *p =new A(1);其实操作也和上面的差不多;
我们之前讲了我们的c中的内存管理函数和我们C++的内存管理函数最大的区别就是是否会调用默认构造函数和析构函数;我们接下来通过看到底层来解释为什么会这样:
new和dlete底层:
我们的new和delete其实在底层是调用了operator new和operator delete函数通过这两个函数来开辟空间,除此之外还会再调用我们的构造和析构函数,不仅如此,它们还会在我们开辟失败时抛异常中止我们的程序(原来c中的需要我们自己来写,并不会抛异常);首先先说说operator new和operator delete;
operator new和operator delete:
我们通过反汇编转到汇编代码,我们看到黄色箭头所指向的代码的下一行我们可以清楚的看到我们的new函数call了operator new这个函数;我们再通过查阅资料我们可以知道operator new代码的底层实现原理:
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);
}
通过上面的代码(我们并不需要读懂这个代码我们只需要看到这里的malloc就可以了),我们可以看到,其实我们的operator new其实底层也是使用的malloc来开辟空间的;它在在开辟失败时还会使用抛异常;
那么同理operator delete也是free来实现它的功能的:
/*
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;
}
我们不需要完全看懂代码我们只需要看到里面free功能就好了;
通过上面的解释,我们再来总结一下,我们的new调用operator new,operator new通过调用malloc来开辟空间如果开辟失败就抛异常,除此之外new还会再调用我们自定义类中的构造函数;我们的delete通过调用operator delete,operator delete通过调用free来释放空间,除此之外delete还会再调用我们自定义类中的析构函数;
malloc和new对比:
相同:
它们都是从堆上开辟空间,并且需要我们自己释放的;
不同:
malloc开辟空间时不可以初始化,但是new可以;
malloc传递地址时需要强制转换类型,但是new不需要它会直接匹配后面所跟着的类型;
malloc开辟空间时需要自己计算大小,但是new不需要,它只需要给我们要开辟空间类型的数量就好了;
malloc是函数而new是操作符;
malloc不能调用构造函数,而我们的new可以;
malloc调用失败会返回null,new失败抛异常;
以上就是对于内存管理的内容
2023.9.26