C++内存
图片来源阿秀的学习笔记
- 栈:函数内局部变量可以存储在栈区,函数执行结束自动释放。栈区内区分配运算内置于处理器指令集中
- 堆:new分配的内存块,由应用程序控制
- 自由存储区:和堆比较像,但是不等价
- 全局数据区、静态存储区:全局变量和静态变量被分配到同一块内存中。C++中,该区定义的变量若是没有初始化,则会被自动初始化。
- 常量存储区:存放常量,不允许修改
- 代码区:存放函数体的二进制代码
malloc和calloc和realloc
malloc
标准C库中,使用malloc和free函数分配释放内存,底层是由brk,mmap,munmap实现。只进行空间申请不初始化
brk是将数据段的最高地址指针向高地址推,mmap是在进程的虚拟地址中(堆和栈中间)找一块空闲的虚拟内存
malloc小于12K的内存使用brk分配,大于128K,使用mmap分配(在堆和栈中间找一块内存分配,brk要等高地址内存释放才能释放,而mmap分配的内存可以单独释放)最高地址空间空闲内存超过128K的时候,执行内存紧缩操作。
malloc从堆上面申请内存。操作系统中有一个记录空闲内存地址的链表,操作系统收到程序的申请时,就会遍历这个链表,找到第一个大于申请空间的堆结点,将该结点从链表中删除,将这个结点的空间分配给程序。
malloc分配内存失败原因
1、内存不足
2、前面程序中出现了内存的越界访问,导致malloc()分配函数所涉及的一些信息被破坏
- 当malloc分配内存不足时,通常有三种方式处理。第一种是判断指针是否为NULL,如果是则马上用return语句终止本函数。第二种是判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。第三种是为new和malloc设置异常处理函数。
- 如果程序出现了内存的越界访问,那么就会导致malloc()分配函数所涉及的一些信息被破坏。下次再使用malloc()函数申请内存就会失败,返回空指针NULL(0)。
高质量的c/c++编程有关malloc分配内存不足的问题
malloc函数分配内存失败的原因及解决方法
void* malloc(unsigned int num_size);//返回类型为void*
int *p = malloc(20*sizeof(int));申请20个int类型的空间;
calloc
函数原型void* calloc(size_t number, size_t size)
;参数number为要申请的个数;size为每一个数据的大小,返回值是void*,通常要强转为我们需要申请空间的类型,开辟成功回返回空间首地址,失败会返回NULL,但是申请成功会对空间进行初始化,且初始为0。
realloc
函数原型void*realloc(void * mem_address, unsigned int newsize)
;
- 参数address为要扩展调整的空间首地址
- 参数newsize为调整为多少字节的空间
- 返回值是void*,通常要强转为我们需要申请空间的类型,开辟成功回返回空间首地址,失败会返回NULL,但是申请成功后并不进行初始化,每个数据都是随机值。
注意的是,之前申请过的空间再用realloc来扩展的话不用释放,只要释放扩展后的空间即可.
1、如果第一个参数是nullptr/NULL,就相当于malloc
2、调整空间大小:如果原来的内存大小后面还有足够的内存空闲用来分配,那么就在原来的后面分配空间;如果没有足够空间了,那么从堆中另外找一块内存,将原来的内存中的内容复制到新的内存中,所以最好把realloc返回的值重新赋值给p,释放原来的空间(注意二次释放的问题)
free
- 被free回收的内存不是立即返回给操作系统的,首先被ptmalloc使用双链表保存起来,用户下一次申请内存的时候,会从这些内存中寻找合适的返回,避免频繁的系统调用,占用过多的系统资源。
ptmalloc 也会尝试对小块内存进行合并,避免过多的内存碎片。
new和delete
首先new和delete都是运算符,不是库函数,不需要单独添加头文件;然后new和delete是通过malloc和free来释放空间的;new申请空间失败的时候会抛出异常,malloc申请空间失败的时候会返回NULL。
new 实现原理
- 调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象。
- 运行该类型的一个构造函数,指定初始化构造对象
- 返回指向新分配并构造后的对象的指针。
operator new ()全局函数原型:
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试
执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
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);
}
动态数组管理new一个数组的时候,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数
new动态数组返回的并不是数组类型,而是一个元素类型的指针
delete 实现原理
- 对指针指向的对象运行适当的析构函数
- 通过调用明为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;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
new和malloc的区别
- malloc和free是标准库函数支持覆盖(所以需要库文件支持),new和delete是运算符,支持重载。
- malloc和free仅仅分配和回收空间,不具备调用构造函数和析构函数功能,分配空间存储类的对象存在风险。
- malloc和free返回的是void指针,需要通过强制类型转换将void转换成我们需要的类型;new和delete返回的是具体类型指针,类型严格与对象匹配。所以new是类型安全的
- malloc需要手工计算出所需要的内存的大小,而new是编译器根据信息自行计算所需内存大小的。
- new申请空间失败的时候会抛出异常,malloc申请空间失败的时候会返回NULL。
allocator
allocator允许我们将分配和初始化分离,使用allocato 通常会提供更好的性能和更灵活的内存管理能力。
new有一些灵活性上的局限性-将内存分配和对象构造组合在一起。当我们分配单个对象,一般希望内存分配和对象初始化组合在一起;但是当分配一大块内存的时候,我们可能更喜欢在这块内存上按需构造对象也就是将内存分配和对象构造分离。我们分配大块内存,但是在真正需要时才真正执行对象的创建操作。因为一般情况下将内存分配和对象构造组合在一起会有一些不必要的开销
头文件
#include<memory>
函数
- allocate分配的内存是未构造的,我们按需在这个内存中构造对象。
- construct成员函数接受一个指针和零个或者多个额外参数,在给定位置构造一个元素。(额外参数是用来初始化构造的对象,类似make_shared参数,这些额外参数必须是与构造的对象的类型相匹配的合法的初始化器)使用未构造的内存,其行为未定义
- 我们用完对象之后,必须对每个构造的元素调用destroy来销毁,destroy接受一个指针,对执行的对象执行析构函数。
- deallocate释放内存,传递给deallocate的指针不能为空,必须指向由allocate分配的内存。而且传递给deallocate的大小参数应该与调用allocate分配内存时提供的大小参数一致。
new[]和delete[]
new[]
new简单类型直接调用operator new分配内存,对于复杂结构,先调用operator new分配内存,然后在分配的内存上调用构造函数;对于简单类型,new[]计算好大小之后调用operator new,对于复杂数据结构,先调用operator new[]分配内存,然后在p的前四个字节写入数组大小n,然后调用n次构造函数。针对复杂类型,new[]额外存储数组大小
delete[]
- delete简单数据类型只是调用free函数,复杂数据类型先调用析构函数再调用operator delete;针对简单类型,delete和delete[]等同
- 为什么new[]要与delete[]对应?因为假设p指向new[]分配的内存,是用了四个字节来存储数组大小,实际分配内存地址为[p-4]系统也是记录这个地址,delete[]实际释放的也是p-4指向的内存,而delete会直接释放p指向的内存这个内存没有被系统记录,所以会崩溃。
malloc和new分配内存的时候,系统都会将分配的内存空间首地址的前四个字节中存储所分配的内存大小值
- delete[]时,数组中的元素按照逆序的顺序进行销毁
- delete[]的时候,可以取出那4个字节空间里面的数,这个数记录了数组大小,然后就知道要调用析构函数多少次了。