博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
c语言---内存各分区+动态内存分配malloc/free和new/delet
阅读量:3920 次
发布时间:2019-05-23

本文共 4557 字,大约阅读时间需要 15 分钟。

存储内容:

静态区域

1、代码段(code segment/text segment)(在链接之后产生)

只读(而在冯诺依曼结构中,代码段(和数据段在一起)→可写)

包含只读的常数变量,例如字符串常量等。

2、只读数据段(RO data)(在链接之后产生)

3、已初始化读写数据段(data / RW data)(在链接之后产生)

数据段data(又名:数据区、静态数据区、静态区----【已初始化读写数据段(RW data)】):静态区的内容在整个程序的生命周期内都存在,由编译器在编译的时候分配

  ①自动全局变量(显式初始化为非零)
  ②static变量(包括static全局和局部变量)

4、bss段(未初始化数据段)(在程序初始化的时候开辟)

Bss:

①显式初始化为0或者
②并未显式初始化(C语言规定未显式初始化的全局变量值默认为0)
data段:显示初始化非零

动态区域(与程序运行过程中分配释放)

1、栈stack:

保存局部变量。栈上的内容只在函数范围内存在,当函数运行结束的时候,这些内容也会自动销毁。

  效率高但是空间大小有限
  存放普通局部变量(而静态局部变量存放在data段)

2、堆heap:

malloc/free new/delete决定  申请后必须释放,否则栈指针释放,对应内存没有释放导致内存泄漏(但程序结束时自动释放)

  使用灵活,空间比较大,但容易出错。

C语言中的全局区(静态区),实际上对应着下述几个段:0、代码段text:代码 字符串常量     (冯诺依曼下可写)1、只读数据段:RO Data  const修饰符的全区变量2、读写数据段:RW Data   有初始值的全局变量3、未初始化数据段:BSS Data   未定义(默认缺省0)or显示为0的全局变量动态内存区:栈:堆:

分段与时间的关系

C语言程序分为映像和运行时两种状态。在编译-连接后形成的映像中,将只包含代码段(Text)、只读数据段(R0 Data)和读写数据段(RW Data)。在程序运行之前,将动态生成未初始化数据段(BSS),在程序的运行时还将动态生成堆(Heap)区域和栈(Stack)区域。

注:1.一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment)。如果不详细区分,统称为段。

2.C语言在编译连接后,将生成代码段(TEXT),只读数据段(RO Data)和读写数据段(RW Data)。在运行时,除了上述三个区域外,还包括未初始化数据段(BBS)区域和堆(heap)区域和栈(Stack)区域。

在这里插入图片描述

(1)C语言中使用char *p = “linux”;定义字符串时,字符串"linux"实际被分配在代码段,也就是说这个"linux"字符串实际上是一个常量字符串(位于代码段)。

const的实现方法至少有2种:

  第一种就是编译将const修饰的变量放在代码段去以实现不能修改(普遍见于各种单片机的编译器);
  第二种就是const型常量和普通变量一样放在数据段(如gcc),但由编译器来检查以确保const型的常量不会被修改

在这里插入图片描述

malloc:从堆申请内存

操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

void* malloc(unsigned size);argv:	unsigned size	=申请的数据量*sizeof(数据类型) //以字节为单位返回申请的内存的首地址(void*)void*表示未确定的类型,在c/c++中void*可以被强转成任意类型的指针。比如:char* p = (char*)malloc(100);一定要检测申请是否成功(可能会失败,返回NULL,原因是内存不足)但不会初始化→可能脏→一般需要搭配memset
malloc(0)👉返回值:失败NULL 成功则返回一个不能用的地址(毕竟空间为0)这个语法是对的,而且确实也分配了内存,但是内存空间是0,就是说返回给你的指针是不能用的但是从操作系统的原理来解释就不奇怪了,这要涉及操作系统维护内存的方法来说了,在内存管理中,内存被分为2部分,栈和堆,栈有自己的机器指令,是一个先进后出的数据结构,我就在这里不再过多解释了,malloc分配的内存是堆内存,由于堆没有自己的机器指令,所以要有系统自己编写算法来管理这片内存,通常的做法是用链表,在每片被分配的内存前加个表头,里面存储了被分配内存的起始地址和大小,你的malloc返回的就是表头里的起始指针,这个地址是由一系列的算法得来了,通常不会为0,一旦分配成功,就返回一个有效的指针,对于分配0空间来说,算法已经算出可用内存的起始地址,但是你占用0空间,所以对那个指针操作就是错误的,操作系统一般不知道其终止地址,因为有占用大小就可以推出终止地址,还有就是即使分配0空间也要释放它,其实是释放的链表结点。还有,返回的指针是可用地址的起始地址,可用大小是固定的,在VC6下是56字节,这个大小可能就是链表的大小。char* ptr = malloc(0*sizeof(char));if(NULL == ptr)      printf("got a NULL pointer");else     printf("got a Valid pointer");当malloc分配内存时👉申请得到:指定SIZE的内存块 + 内存块管理信息(VC6中为64BYTE,用于维护该内存块)。因此,malloc(0)返回一个合法的指针并指向存储内存块信息的额外内存,我们当然可以在该内存上进行读写操作,但是这样做了会破坏该内存块的维护信息,因此当我们调用free(ptr)时就会出现错误。

void* calloc(size_t numElements, size_t sizeOfElements);(元素的个数, 单个元素的字节数)

空间的大小为:元素的个数*单个元素的字节数。
  能够初始化calloc(全部清零)

void* realloc(void* ptr, unsigned newsize);(地址,字节数)

给一个已经分配地址的指针重新分配空间,参数ptr为原有的空间指针,newsize为重新申请的地址长度。它与malloc的区别就是如果你给的指针是NULL,那么你使用的就是malloc,如果你给出的指针是一个已经分配了地址的指针(ptr),那么你使用的就是realloc。
  realloc可以对给定的指针所指向的空间进行扩大或者缩小,无论是扩大还是缩小,原有内存中的内容将保持不变(如果对于缩小之后的空间,被缩小的那部分空间内的数据还是会丢失)。realloc并不保证调整后的内存空间和原来的内存空间保持同一个地址。相反,realloc指针很可能指向一个新的地址。【realloc是从堆上分配空间的,但当你进行扩大的时候,realloc会试图从堆上现存的数据后面的那些字节中获取附加的字节,如果能满足,就刚好。但如果后面的字节数不够,其就会使用堆上第一个有足够大小的自由块,然后将现存的数据拷贝到新的位置,将老块放回到堆上。在这个过程中,数据会被移动。也就是说,当你使用realloc的时候,数据可能被移动。】

堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!
void Function(void){		char *p = (char *)malloc(100 * sizeof(char));}指针p在栈上,而p指向的内容却在堆中函数结束后,函数所在的栈被销毁,指针跟着销毁,这与堆中的内存无关,堆中的内存依旧存在

内存释放

free(p);指针依旧存放着地址,内存中的数据也没有改变-----下一次malloc后要记得清理memset作用:free斩断指针和指向的内存的关系→→没有所有权→不能进行操作  free后【p依旧存放那一个地址】,一定要把p指针置为NULL(不然就是野指针)

申请的时候实际上占用的内存要比申请的大。

因为超出的空间是用来记录对这块内存的管理信息。
大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

malloc()申请的空间有2部分:

 ①用来记录管理信息的空间(结构体)
 ②申请的空间
free()就是根据这个结构体的信息来释放malloc()申请的空间
https://www.jianshu.com/p/7735cac35e9e

malloc申请的内存不能多次free释放→会出错(而多次释放NULL空指针则没问题)

new/delete

区别:

  1.它们都是动态管理内存的入口。
  2.malloc/free是c/c++标准库的函数,new/delete是c++操作符。
  3.malloc/free只是动态分配/释放内存空间。而new/delete出来分配空间还会调用构造函数和析构函数进行初始化与清理。
  4.malloc/free需要手动计算类型大小且会返回void*, new/delete可以自己计算类型的大小,返回对应类型的指针。
  我们在c++中是允许进行重载的,那我们也可以重载一下new和delete,我在这就不做了(其实new和delete是不能重载的,即使你进行了重载,也只是重载了operator new和operator delete)。

有关operator new/operator delete operator new[]/operator delete[]

总结:

  1.operator new/operator delete operator new[]/operator delete[]的用法和malloc/free一样。
  2.它们只负责分配空间/释放空间,不会调用对象构造函数和析构函数来初始化/清理对象。
  3.实际operator new/operator delete 只是malloc和free的一层封装。

五、new和delete在内存中所做的事

new做的事:

1.调用operator new分配空间
2.调用构造函数初始化空间

delete做的事:

1.调用析构函数清理对象
2.调用operator delete释放空间

new[N]做的事:

1.调用operator new分配空间
2.调用N次构造函数分别初始化每个对象

delete做的事:

1.调用N次析构函数清理对象
2.调用operator delete释放空间

在这里插入图片描述

转载地址:http://iuhrn.baihongyu.com/

你可能感兴趣的文章
深入探究.Net Core Configuration读取配置的优先级
查看>>
Blazor带我重玩前端(六)
查看>>
使用 C# 捕获进程输出
查看>>
数据库单表千万行 LIKE 搜索优化手记
查看>>
.NET Core 中生成验证码
查看>>
.NET Core 中导入导出Excel
查看>>
初识ABP vNext(8):ABP特征管理
查看>>
WPF 消息框 TextBox 绑定新数据时让光标和滚动条跳到最下面
查看>>
【BCVP】实现基于 Redis 的消息队列
查看>>
网络安全逐渐成为程序员的必备技能
查看>>
统信发布UOS V20 进军个人市场 生态日益完善
查看>>
BeetleX框架详解-小结
查看>>
拥抱.NET 5,从自研微服务框架开始
查看>>
C# 中的 is 真的是越来越强大,越来越语义化
查看>>
简单理解CAP-BASE
查看>>
gRPC-微服务间通信实践
查看>>
Firefox 18周岁
查看>>
IdentityServer4系列 | 初识基础知识点
查看>>
为什么我们总是「习惯性辩解」?
查看>>
.NET 异步解说
查看>>