new、delete和malloc、free

C++内存

图片来源阿秀的学习笔记
在这里插入图片描述

  • 栈:函数内局部变量可以存储在栈区,函数执行结束自动释放。栈区内区分配运算内置于处理器指令集中
  • 堆:new分配的内存块,由应用程序控制
  • 自由存储区:和堆比较像,但是不等价
  • 全局数据区、静态存储区:全局变量和静态变量被分配到同一块内存中。C++中,该区定义的变量若是没有初始化,则会被自动初始化。
  • 常量存储区:存放常量,不允许修改
  • 代码区:存放函数体的二进制代码

malloc和calloc和realloc

malloc

标准C库中,使用malloc和free函数分配释放内存,底层是由brk,mmap,munmap实现。只进行空间申请不初始化

brk是将数据段的最高地址指针向高地址推,mmap是在进程的虚拟地址中(堆和栈中间)找一块空闲的虚拟内存
malloc小于128K的内存使用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));申请20int类型的空间;

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 实现原理

  1. 调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象。
  2. 运行该类型的一个构造函数,指定初始化构造对象
  3. 返回指向新分配并构造后的对象的指针。

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 实现原理

  1. 对指针指向的对象运行适当的析构函数
  2. 通过调用明为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的区别

  1. malloc和free是标准库函数支持覆盖(所以需要库文件支持),new和delete是运算符,支持重载。
  2. malloc和free仅仅分配和回收空间,不具备调用构造函数和析构函数功能,分配空间存储类的对象存在风险。
  3. malloc和free返回的是void指针,需要通过强制类型转换将void转换成我们需要的类型;new和delete返回的是具体类型指针,类型严格与对象匹配。所以new是类型安全的
  4. malloc需要手工计算出所需要的内存的大小,而new是编译器根据信息自行计算所需内存大小的。
  5. 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个字节空间里面的数,这个数记录了数组大小,然后就知道要调用析构函数多少次了。

C++中的几种new

plain new

普通的new,空间分配失败的时候抛出异常bad_alloc而不是返回NULL

void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void *) throw();

nothrow new

空间分配失败的时候不是抛出异常而是返回NULL

void * operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void*) throw();

placement new

允许在一块已经分配成功的内存上重新构造对象或者对象数组。不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。

//定义如下
void* operator new(size_t,void*);
void operator delete(void*,void*);

主要用途是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组;构造起来的对象数组,要显式的调用他们的析构函数来销毁(析构函数并不释放对象内存)。
千万不要使用delete!因为placement new构造起来的对象或者数组大小并不一定等于原来分配的内存大小,使用delete可能会造成内存泄漏或者之后释放内存时出现运行错误

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值