首页 > 系统相关 >动态内存管理

动态内存管理

时间:2024-09-26 23:49:42浏览次数:9  
标签:malloc 管理 int free 动态内存 空间 NULL ptr

目录

1.为什么会有动态内存管理

2.malloc和free

2.1   malloc

2.2   free

3.calloc和realloc

3.1   calloc

3.2   realloc

4.常见的动态内存的错误                                                                                                                                                                                                          

4.1 对NULL指针的解引用

4.2 对动态开辟空间的越界访问

4.3 对非动态开辟内存使用free

4.4 使用free释放一块动态开辟内存的一部分

4.5 对同一块内存多次释放

4.6 动态开辟内存后没有释放(内存泄漏)

5.柔性数组

5.1柔性数组的特点

5.2柔性数组的使用

5.3柔性数组的优势

6.总结C/C++程序中内存区域划分


 1.为什么会有动态内存管理

我们已经熟练的两种空间开辟方式:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

上面两个空间开辟方式有些特点:                                                                                                                                                                                             •空间开辟的大小是固定的                                                                                                                                                                                                         •数组在创建时,就要明确指出数组的大小,一旦创建就不能再改,但是这两个开辟方式太固定化了,有时候我们的程序运行时才能知道需要多少空间大小,那数组编译时开辟空间的大小就不适用了。C语言引入了动态内存管理,让我们可以申请和释放空间,这就非常灵活了。

2.malloc和free

2.1   malloc

C语言提供了一个动态内存开辟函数:

void* malloc (size_t size);

该函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。                                                                                                                               • 如果开辟成功,则返回指向新空间的指针                                                                                                                                                                             • 如果开辟失败,则返回NULL指针,所以检查空间开辟是否成功是必要的                                                                                                                                 • 返回值类型是void*,所以malloc函数并不知道创开辟空间的类型,具体要我们自己来决定                                                                                                     • 如果size为0,malloc的行为是标准未定义的,具体看编译器

2.2   free

C语言还提供了一个函数free,用来释放和回收动态开辟的空间:

void free (void* ptr);

free函数是释放和回收动态开辟的内存。                                                                                                                                                                                   • 如果ptr是NULL指针,函数啥都不做                                                                                                                                                                                       • 如果ptr指向的空间不是动态开辟的,free的行为未定义                                                               

malloc和free函数都声明在stdlib.h头文件里。   

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
 int num = 0;
 scanf("%d", &num);
 int arr[num] = {0};
 int* ptr = NULL;
 ptr = (int*)malloc(num*sizeof(int));
 if(NULL != ptr)//判断ptr指针是否为空
 {
 int i = 0;
 for(i=0; i<num; i++)
 {
 *(ptr+i) = 0;
 }
 }
 free(ptr);//释放ptr所指向的动态内存
 ptr = NULL;//是否有必要?
 return 0;
}

ptr=NULL这一步很有必要,因为我们释放了ptr指向的空间,如果再依靠ptr访问该空间,就属于野指针访问,将导致未定义行为。

3.calloc和realloc

3.1   calloc

C语言还提供了一个函数calloc,calloc也是用来动态内存分配的。                                                   

void* calloc (size_t num, size_t size);
 •该函数参数意思是为num个size大小的元素开辟空间,并把空间里的每个字节都初始化为0                                                                                                   •该函数和malloc唯一的区别就是calloc会在返回地址之前把申请的空间里的字节都初始成0 比如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
 int *p = (int*)calloc(10, sizeof(int));
 if(NULL != p)
 {
 int i = 0;
 for(i=0; i<10; i++)
 {
 printf("%d ", *(p+i));
 }
 }
 free(p);
 p = NULL;
 return 0;
}

输出结果:

 0 0 0 0 0 0 0 0 0 0

所以如果我们需要对开辟的空间初始化,可以直接使用calloc很好的完成任务。

3.2   realloc

realloc使动态内存管理变得更加灵活,前面介绍的malloc和calloc都是申请空间,而realloc可以调整动态开辟的空间大小。

void* realloc (void* ptr, size_t size);
• ptr 是要调整的内存地址 • size是调整之后空间的大小 • 返回值为调整之后的内存起始位置。 • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。 • realloc在调整内存空间的是存在两种情况: 情况1:原有空间之后有足够大的空间 情况2:原有空间之后没有足够大的空间 情况1呢就是直接在原来的空间后面追加空间,初始地址不变,原来的数据不变。 情况2呢就是原来空间加上后面的空余空间也不够,这时候只能在堆空间上找一段合适的空间来使用,这时候返回新的内存地址。

也是因为有上面两种情况的发生,因此我们使用realloc函数的时候需要小心点。

#include <stdio.h>
#include <stdlib.h>
int main()
{
 int *ptr = (int*)malloc(100);
 if(ptr != NULL)
 {
 //业务处理
 }
 else
 {
 return 1; 
 }
 //扩展容量
 
 //代码1 - 直接将realloc的返回值放到ptr中
 ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
 
 //代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
 int*p = NULL;
 p = realloc(ptr, 1000);
 if(p != NULL)
 {
 ptr = p;
 }
 //业务处理
 free(ptr);
 return 0;
}

直接将realoc的返回值放到ptr里是不可以的,因为可能申请失败,这时候返回的就是NULL,ptr=NULL,我们就找不到原来的空间了,所以需要创建一个新指针接收。

4.常见的动态内存的错误                                                                                                                                                                                                          

4.1 对NULL指针的解引用

void test()
 {
 int *p = (int *)malloc(INT_MAX/4);
 *p = 20;//?
 free(p);
 }

这个就是上面说到的问题,我们不能确定空间是否申请成功,如果失败返回NULL,那么指针p就是空指针,不能对其解引用。

4.2 对动态开辟空间的越界访问

void test()
 {
 int i = 0;
 int *p = (int *)malloc(10*sizeof(int));
 if(NULL == p)
 {
 exit(EXIT_FAILURE);
 }
 for(i=0; i<=10; i++)
 {
 *(p+i) = i;//?
 }
 free(p);
 }

这个还是很明显的,一共就申请了10个int类型大小的空间,也就是当i=9时,访问到最后一个元素,结果i还能等于10,这肯定就越界了啊。

4.3 对非动态开辟内存使用free

void test()
 {
 int a = 10;
 int *p = &a;
 free(p);//?
 }

指针p是局部变量,不能释放,可能会出现使程序崩溃等问题。(free不能释放动态开辟内存以外的内存)

4.4 使用free释放一块动态开辟内存的一部分

void test()
 {
 int *p = (int *)malloc(100);
 p++;
 free(p);//?
 }

因为p++,所以p已经不指向这块动态内存的起始位置了,使用free不能释放这整块动态内存。

4.5 对同一块内存多次释放

void test()
 {
 int *p = (int *)malloc(100);
 free(p);
 free(p);//?
 }

这个问题也很严重,可能导致程序运行异常或崩溃。

4.6 动态开辟内存后没有释放(内存泄漏)

void test()
 {
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
 }
int main()
 {
 test();
 while(1);
 }

这个程序里动态申请了空间,直到最后也没有手动释放,这个问题很经典,这只是一小段代码而且没有合作,假如我们在公司里一起编写一个软件,那么我的这段代码申请了一块空间,他的代码申请一块空间......大家都不释放,那么肯定会出现问题(当程序结束时,OS会回收空间)。

所以我们申请了空间就要记得释放,养成好习惯。

5.柔性数组

可能你没听过柔性数组这么一个东西,但它的确存在,在C99里,结构体里最后一个成员是允许是不知大小的数组,这就是柔性数组成员。

struct st_type
{
 int i;
 int a[0];//柔性数组成员
};

不过上面的写法在一些编译器可能出错,还有种写法:

struct st_type
{
 int i;
 int a[];//柔性数组成员
};

5.1柔性数组的特点

1.柔性数组的前面至少有一个其他成员                                                                                                                                                                              2.sizeof计算结构体的大小不包含柔性数组大小                                                                                                                                                                        3.包含柔性数组的结构体用malloc进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小

比如:

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
int main()
{
 printf("%d\n", sizeof(type_a));//输出的是4
 return 0;
}

5.2柔性数组的使用

//代码1
#include <stdio.h>
#include <stdlib.h>
int main()
{
 int i = 0;
 type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
 //业务处理
 p->i = 100;
 for(i=0; i<100; i++)
 {
 p->a[i] = i;
 }
 free(p);
 return 0;
}

这样的柔性数组成员a就获得了100个整形元素的连续空间。

5.3柔性数组的优势

上面的type_a结构也可以设计成下面的结构,有着一样的效果。但是这里我们可以看出来两种写法有着好坏,第一种会更简便一点,因为只用申请释放一次内存,而下面这种结构需要申请释放两次空间。

//代码2
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
 int i;
 int *p_a;
}type_a;
int main()
{
 type_a *p = (type_a *)malloc(sizeof(type_a));
 p->i = 100;
 p->p_a = (int *)malloc(p->i*sizeof(int));
 
 //业务处理
 for(i=0; i<100; i++)
 {
 p->p_a[i] = i;
 }
 
 //释放空间
 free(p->p_a);
 p->p_a = NULL;
 free(p);
 p = NULL;
 return 0;
}

6.总结C/C++程序中内存区域划分

C/C++程序内存分配的几个区域:

1. 栈区(stack):在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时 这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内 存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等。 《函数栈帧的创建和销毁》 2. 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅ 式类似于链表。 3. 数据段(静态区):(static)存放全局变量、静态数据。程序结束后由系统释放。 4. 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。

标签:malloc,管理,int,free,动态内存,空间,NULL,ptr
From: https://blog.csdn.net/wangzhezhifwng/article/details/142352753

相关文章

  • 信息安全工程师(20)密码管理与数字证书
    一、密码管理    密码管理是确保密码安全、有效和合规使用的关键过程。它涉及密码的创建、存储、使用、更改和销毁等各个环节。1、主要内容密码策略:制定和执行严格的密码策略,如密码长度、复杂度、有效期和更换频率等要求,以提高密码的安全性。密码存储:采用安全的密......
  • 【ROS2】ROS命令管理器RCM
    1、简述安装、使用ROS时,经常因为国外源问题,导致失败;Ubuntu不同的版本也要使用不同的ROS版本;源码编译ROS时,问题会更多……为了解决上述问题,国内的创客智造(ncnynl)制作了ROS命令管理器RCM,用来解决上述问题。官网:https://www.ncnynl.com/archives/202206/5316.html2、安装......
  • 基于Node.js+vue基于java的校园疫情管理系统(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容研究背景近年来,全球范围内频繁爆发的疫情事件对教育领域产生了深远影响,特别是在校园环境中,如何有效防控疫情、保障师生健康安全成为了亟待解决的问题。传统的校园管......
  • PasteForm最佳CRUD实践,实际案例PasteTemplate详解之管理前端的代码(二)
    之前的文章说了,使用反射和ABPvNext的Dto实现用后端控制前端以实现最佳CRUD实践!相信看过一的已经了解了这个PasteForm是如何实现的了,本文来看下具体如何实现的表格页面的实现打开pasteform/index.html页面之后,先会向API请求当前的path的数据模板_apiget(`/api/app/${_class......
  • Java毕业设计-基于SSM框架的超市会员(积分)管理系统项目实战(附源码+论文)
    大家好!我是岛上程序猿,感谢您阅读本文,欢迎一键三连哦。......
  • 如何打造流浪天使乐园管理系统?Java SpringBoot+Vue技术解析
    ✍✍计算机毕业编程指导师**⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!⚡⚡Java、Python、小程序、大数据实战项目集⚡⚡文末获取......
  • Java毕业设计-基于SSM框架的供电所档案管理系统项目实战(附源码+论文)
    大家好!我是岛上程序猿,感谢您阅读本文,欢迎一键三连哦。......
  • 技术人的福音!深度对比10款工业项目管理软件,为你的项目保驾护航
    市面上主流的10款工业项目管理系统推荐:PingCode、Worktile、Smartsheet、Wrike、ProofHub、ZohoProjects、ClickUp、致远OA、用友协同云、金蝶EAS。在选择项目管理系统时,许多小型企业和初创公司常常面临着难以决策的痛点:如何找到既能提高团队效率又易于操作的工具呢?一个合......
  • 掌握 Lerna:管理 JavaScript Monorepos 的指南
    目录简介第一章:lerna是什么?为什么选择monorepos?第2章:安装和设置lerna先决条件分步安装指南设置您的第一个lerna项目第3章:lerna中的依赖关系管理独立依赖提升共享依赖项引导包第4章:跨包运行脚本全局执行脚本针对特定包第5章:使用lerna进行版本控制和发布固定模式与......
  • 当超级管理员因错误而不允许用户登录时会发生什么
    想象一下,您是网络应用程序的用户,兴奋地导航到登录页面以访问您的帐户,然后输入凭据,单击闪亮的“登录”按钮,然后满怀期待地等待。但可惜的是,您的个性化仪表板并没有迎接您,而是出现了一条不祥的错误消息:“抱歉,由于系统错误,站点管理员已暂时禁用用户登录。”当您感到沮丧时,您可能想知......