160-详细学习动态内存

什么是动态内存?

在这里插入图片描述
运行时崩溃
栈区:我们知道栈区在函数被调分配时,用于存放函数的参数值,局部变量等值,在Windows中栈的默认大小是1M,在VS中可以设置栈区的大小。在Linux中栈的默认大小是10M,在gcc编译时可以设置栈区的大小。
堆区:程序运行时可以在堆区动态地申请一定大小的内存,并在用完之后归还给堆区。在Linux系统中堆区的大小接近3G
一般情况下,我们需要大块内存或程序在运行的过程中才知道所需内存大小,我们就从堆区分配空间。

进程的堆栈大小:

32位Windows,一个进程栈的默认大小是1M,在vs的编译属性可以修改程序运行时进程的栈大小。
Linux下进程栈的默认大小是10M,可以通过 ulimit -s查看并修改默认栈大小。 默认一个线程要预留1M左右的栈大小,所以进程中有N个线程时,Windows下大概有N*M的栈大小。堆的大小理论上大概等于进程虚拟空间大小-内核虚拟内存大小。
windows下,进程的高位2G留给内核,低位2G留给用户,所以进程堆的大小小于2G。Linux下,进程的高位1G留给内核,低位3G留给用户,所以进程堆大小小于3G。
进程的最大线程数:

32位windows下,一个进程空间4G,内核占2G,留给用户只有2G,一个线程默认栈是1M,所以一个进程最大开2048个线程。当然内存不会完全拿来做线程的栈,所以最大线程数实际值要小于2048,大概2000个。
32位Linux下,一个进程空间4G,内核占1G,用户留3G,一个线程默认8M,所以最多380个左右线程。

动态内存分配函数

C语言中动态内存管理的有4个函数:
malloc,calloc,realloc,free都需要引用stdlib.h或malloc.h文件

malloc函数
malloc向堆区申请一块指定大小的连续内存空间
void* malloc(size_t size);//typedef unsigned int size_t;
分配size字节的未初始化的内存
若分配成功,则返回为任何拥有基础对齐的对象类型对齐的指针
若size为0,则malloc的行为是实现定义的。例如可返回空指针。也可返回非空指针;但不应当解引用这种指针,而且应该将它传递给free以避免内存泄漏。

malloc是线程安全的,它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储
令free或ralloc归还一块内存区域的先前调用,同步于令malloc分配相同或部分相同的内存区域的调用。此同步出现于任何通过解分配函数所作的内存访问后,和任何malloc所作的内存访问前。所有操作每块特定内存区域的分配和解分配函数有单独全序。

参数
size要分配的字节数
返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用free()或realloc()解分配返回的指针
失败时,返回空指针

free用来释放从malloc,realloc,calloc成功获取到的动态内存分配的空间。
void free(void *ptr);
释放 之前由malloc(),calloc(),aligned_alloc()(C11起)或realloc()分配的空间。
若ptr为空指针,则函数不进行操作。
若ptr的值不是之前从malloc(),calloc(),realloc()或aligned_alloc()(C11起)返回的值,则行为未定义。
若ptr所指代的内存区域已经被解分配,则行为未定义,即是说已经以ptr为参数调用free()或realloc,而且没有后继的malloc(),calloc()或realloc()调用以ptr为结果。
若在free()返回后通过指针ptr访问内存,则行为未定义(除非另一个分配函数恰好返回等于ptr的值)。

free线程是安全的它表现得如同只访问通过其参数可见的内存区域,而非静态存储。
令free解分配内存区域的调用,同步于任何令分配函数分配相同或部分相同区域的后续调用,这种同步出现于任何解分配函数所做的内存访问后,和任何分配函数所做的内存访问前。所有操作每块特定内存区域的分配及解分配函数拥有单独全序。
参数
ptr指向要解分配的内存的指针
返回值(无)
注解:此函数接收空指针(并对其不处理)以减少特例的数量。不管分配成功与否,分配函数返回的指针都能传递给free()
在这里插入图片描述

calloc分配并使用0初始化连续内存空间
void*calloc(size_t num,size_t size);
为num个对象(元素)的数组分配内存,并初始化所有分配存储中的字节为0
若分配成功,会返回指向分配内存块最低位(首位)字节的指针,它为任何类型适当地对齐
若size为0,则行为是实现定义的(可返回空指针,或返回不可用于访问存储的非空指针)

扫描二维码关注公众号,回复: 12475943 查看本文章

calloc是线程安全的它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储。
令free或realloc解分配一块内存区域的先前调用,同步于令calloc分配相同或部分相同的内存区域的调用。这种同步出现于任何解分配函数所做的内存访问之后,和任何calloc所做的内存访问之前。所有操作每块特定内存区域的分配及解分配函数拥有单独全序(C11起)

参数
num:对象(元素)数目
size:每个对象(元素)的大小

返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用free()或realloc()解分配返回的指针。
失败时,返回空指针

在这里插入图片描述
使用memset函数:将内存单元置为0值

我们使用malloc或calloc申请了堆空间内存,但是程序在运行过程中发现申请空间少了或多了如何处理?
realloc扩充之前分配的内存块(重新分配内存块)
void* realloc(void *ptr,size_t new_size);
重新分配给定的内存区域,它必须是之前为malloc(),calloc(),或realloc()所分配,并且仍未被free或realloc的调用所释放。否则,结果未定义
重新分配按以下二者之一执行:
(a):可能话,扩展或收缩ptr所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变。若扩张范围,则数组新增部分的内容是未定义的。
(b):分配一个大小为new_size字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块

若无足够内存,则不释放旧内存块,并返回空指针
若ptr是NULL,则行为与调用malloc(new_size)相同
若new_size为0,则行为是实现定义的(可返回空指针,此情况下可能或可能不释放旧内存,或返回不会用于访问存储的非空指针)

realloc是线程安全的。它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储。
先前令free或realloc归还一块内存区域的调用,同步于任何分配函数的调用,包括分配相同或部分相同内存区域的realloc。这种同步出现于任何解分配函数所做的内存访问后,和任何realloc所做内访问前。所有操作一块特定内存区域的分配及解分配函数拥有单独全序

参数
ptr:指向需要重新分配的内存区域的指针
new_size:数组的新大小(字节数)

返回值
成功时,返回指向新分配内存的指针。返回的指针必须用free()或realloc()归还。原指针ptr被非法化,而且任何通过它的访问是未定义行为(即使重新分配是就地的)
失败时,返回空指针。原指针ptr保持有效,并需要通过free()或realloc()归还

在这里插入图片描述

realloc函调整内存大小分为3种情况

第一种情况:后续未分配内存空间足够大,可以分配空间,图示如下:
在这里插入图片描述
第二种情况:后续未分配内存空间不足够大,不能分配空间,图示如下:
在这里插入图片描述
第三种情况:堆内存不足,扩展空间失败,realloc函数返回NULL
在这里插入图片描述

aligned_alloc分配对齐的内存(C11)

void *aligned_alloc(size_t alignment,size_t size);
分配size字节未初始化的存储空间,按照alignment指定内存。size参数必须是alignment的整数倍

aligned_alloc是线程安全的:它表现得如同只访问通过其参数可见的内存区域,而非任何静态存储。
令free或realloc归还一块内存区域的先前调用,同步于令aligned_alloc分配相同或部分相同的内存区域的调用。此同步出现于任何解分配函数所做的内存访问之后,和任何通过aligned_alloc所做的内存访问之前。所有操作每块特定内存区域的分配及解分配函数拥有的单独全序

参数
alignment:指定对齐。必须是实现支持的合法对齐
size:分配的字节数。alignment的整数倍

返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,返回的指针必须用free()或realloc()解分配
失败时,返回空指针
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

栈区和堆区的区别

1、管理方式:栈由系统自动管理,堆由程序员控制,使用方便,但易产生内存泄漏
2、生长方向:栈由低地址扩展(即向下生长),是连续的内存区域;堆向高地址扩展(即向上生长),是不连续的内存区域。这是由于堆区管理系统用链表来存储空闲内存地址,自然不连续
3、空间大小:栈顶地址和栈的最大容量由系统预先规定(通常默认1M或10MM);堆的大小则受限于计算机系统中有效的虚拟内存,32位Linux系统中堆内存可达2.9G空间
4、存储内容:栈在函数调用时,首先压入是函数实参,然后主调函数中下条命令(函数调用语句的下条执行语句)的地址压入,最后是被调函数的局部变量。本次调用结束后,局部变量先出栈,最后栈平衡,程序由该点继续运行下条可执行语句。堆通常在头部用一个字节存放其大小,堆用于存储生存期与函数调用无关的数据,具体内容由程序员安排。
5、分配方式:栈可静态分配或动态分配。静态分配由编译器完成,如局部变量的分配。动态分配由alloca函数在栈上申请空间,用完后自动释放不需要调动free函数。堆只能动态分配并且手工释放。
6、分配效率:栈由计算机底层提供支持:分配专门的寄存器存放栈地址,压栈出栈由专门的指令执行,因此效率较高。堆由函数库提供,机制复杂,效率比栈低得多。
7、分配后系统响应:只要栈剩余空间大于所申请空间,系统将为程序员提供内存,否则报告异常提示栈溢出。
操作系统为堆维护一个记录空闲内存地址的链表。当系统收到程序的内存分配申请时,会遍历该链表寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并把该结点空间分配给程序。若无足够大小的空间(可能由于内存碎片太多),有可能调用系统功能去增加程序数据段的内存空间,以便有机会分到足够大小的内存,然后进行返回。大多数系统会在该内存空间首地址处记录本次分配的内存大小,供后续的释放函数(如free/delete)正确释放本内存空间。
此外,由于找到的堆结点大小不一定正好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。
8、碎片问题:栈不会存在碎片问题,因为栈是先进后出的队列,内存块弹出栈之前,在其上面的后进的栈内容已弹出。而频繁申请释放操作会造成堆内存空间的不连续,从而造成大量碎片,使程序效率降低。

可见,堆容易造成内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和内核态的切换,内存申请的代价更为昂贵。所以栈在程序中应用最广泛,函数调用也是利用栈来完成,调用过程中的参数,返回地址,栈基指针和局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间的时候使用堆。

最后使用栈和堆时应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的结果。

动态内存管理与结构体

结构体变量和内置类型都有局部,全局,动态生存期
在这里插入图片描述
结构体也可以嵌套指向自身的指针
在这里插入图片描述

柔性数组

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/LINZEYU666/article/details/113372411