动态内存管理及应用总结篇
一篇博客学好动态内存的管理和使用
这篇博客干货满满,建议收藏再看哦!!
求个赞求个赞求个赞求个赞 谢谢

先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力 看完之后别忘记关注我哦!️️️
本篇基本上涵盖了初学阶段动态内存的所有知识点,喜欢的伙伴一定要看完哦

强烈建议本篇收藏后食用~

本章重点
动态内存函数介绍
动态内存常见错误及其避免
柔性数组

动态内存函数介绍

想要学习动态内存分配,我们一定要知道,动态内存是怎么开辟的,是怎么释放的。

malloc()和free()

malloc()的原型void* malloc (size_t size);
使用malloc函数,我们可以直接在堆上申请我们想要大小的空间。
注意,用malloc()开辟的空间,是在内存的堆上的,而不是栈上的
如果malloc()开辟空间成功,会返回新开辟空间的地址,如果开辟失败,会返回空指针。
每次使用完在堆上开辟的空间后,一定要记得释放刚刚的空间,把它还给计算机。用的是free()函数

int main()
{
int*p=(int*)malloc(10 * sizeof(int));//堆区开辟空间
//但是void*是不能解引用的
//使用这些空间之前,一定要记得检验malloc()是否开辟空间成功
if (p == NULL)
{
perror("main");
return 1;//结束函数
}
//使用
//...
//回收空间
free(p);
//但是p是不会自动把p置成NULL?
//答案是不会,为了防止以后非法访问
p = NULL;//自己动手把p置成NULL
return 0;
}

这就是用malloc()开辟空间,使用完用free()释放空间的一个标准的使用模板

以下是一些需要注意的点。
1.使用malloc()堆上空间之后,一定要记得检验是否开辟失败,否则可能将会造成对空指针解引用的问题。
2.使用完堆上空间后,一定要记得用free()函数释放,把空间还给内存。

注意:free()只能释放动态开辟的空间,也就是堆上的空间,不能用来释放栈上的空间

int main()
{
int a = 10;
free(&a);//错误
return 0;
}

所以综上:malloc()和free()要成对使用

calloc()

先来看函数原型:void* calloc (size_t num, size_t size);
我们打开www.cplusplus.com可以看到
该函数的作用是Allocate and zero-initialize array也就是在堆上开辟一个初始化为0的一个数组
两个参数分别是,想要开辟的元素个数,和每个元素的大小

int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)//检验是否开辟成功
{
perror("main");
return 1;
}
//使用过程
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i)=5;
}
//回收空间
free(p);
p=NULL;
return 0;
}

与malloc()不同的是,calloc()函数开辟的空间已经初始化为0了,而malloc()开辟的空间是没有初始化的。我们可以自己写一段小代码,f10运行后打开内存窗口就可以看到了

realloc()

realloc函数的功能是,Changes the size of the memory block pointed to by ptr.也就是改变空间的 大小
先来看realloc函数的原型:void* realloc (void* ptr, size_t size);
第一个参数就是指向一块儿动态内存的指针,第二个参数就是想要设置的该空间的新大小。
我们在刚才上面这段代码的基础上,讲解我们的realloc()函数

int main()
{
//int* p = (int*)malloc(40);//还没初始化
int* p = (int*)calloc(10,sizeof(int));//初始化了
if (p == NULL)
{
perror("main");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i)=5;
}
//还想再来十个整型的地址
//realloc来调整空间
int*ptr=(int*)realloc(p, 20 * sizeof(int));
//追加空间
//判断追加的空间是否追加成功
if (ptr != NULL)
{
p = ptr;
}
//使用
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}

然而,我们需要掌握的realloc()的使用和理解并不只是这么简单
使用realloc()追加空间的时候常常会有以下这几种情况

1.如果realloc()发现原来p指向的空间后面空间足够——直接增容并且返回原地址
2.如果realloc()发现p指向的空间后面的空间不够的时候,
它会重新找一块新的空间,
并且把原来的内容拷贝下来,放到新找到的空间里面去,
并且把旧地址释放。
3.如果在整个堆区里面找不到合适空间的时候,就返回空指针。
所以需要注意的是:
一般不能直接用原来的p来接受realloc()的返回值,
万一堆里找不到空间,还把原来的p搞没了
如果这样,我们就永远找不到一开始开辟的空间了
所以一般创建一个临时指针int*ptr来接收realloc()函数的返回值
并且判断开辟成功以后,再把ptr赋给p即可。

动态内存使用常见错误及其避免

关注过我的小伙伴知道,我已经在发布这篇博客之前已经发过一篇动态内存的避雷篇了,跟以下内容是一样的,小伙伴们可以去我的另外一篇博客了解这部分内容,也可以在这里继续往下看哦

对空指针的非法解引用

一般来说,有一定基础的我们都不会故意解引用空指针,但是,我们在使用动态内存开辟的时候,可能会解引用到”无形“的空指针

int main()
{
int*p=(int*)malloc(10000000000000000000);
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;//err
}
return 0;
}

在上面这个代码中,看似没有什么问题。但是我们知道malloc函数是有可能开辟动态内存空间失败的,我们一口气开辟这么多字节的空间,我们堆上空间不够用了,malloc函数就会开辟失败,我们通过www.cplusplus.com网站搜索malloc函数我们可以知道,如果函数开辟失败,会返回空指针,那么这个时候,我们的p就是一个空指针,空指针怎么能解引用呢?
我们可以在for之前用一个perror()函数来查看我们的问题perror("main:");


为了避免在编写程序的时候遇到这个问题,我们可以用assert()断言后再使用该指针,或者使用if语句,当指针为真的时候再使用该指针

越界访问问题

int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
//越界访问,我们只开辟了40个字节,不是四十个整型
for (i = 0; i < 40; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}

正如这个代码,我们必须知道malloc函数在堆上开辟的时候的单位是字节,而我们这里却要访问40个整型,也就是160个字节,这就越界访问了
每写一步我们思考清楚就可以避免这个问题。

使用free释放非动态开辟的空间

free只能用来释放堆上的动态开辟的空间,而不能释放栈上的空间,栈上的内存空间,在函数结束,程序结束都会自动释放的。

//3.使用free释放非动态开辟的空间
int main()
{
int arr[10] = { 0 };
int* p = arr;
free(p);//err
p = NULL;
return 0;
}

每写一步我们思考清楚就可以避免这个问题。

使用free释放动态内存中的一部分

释放了,但没完全释放

//4.使用free释放动态内存中的一部分
int main()
{
int* p = malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*p++ = i;//我的p不见了
}
free(p);//err
p = NULL;
return 0;
}

*在这个代码中,由p指针指向的动态内存开辟后,我们的p在后续操作中改变了它自己的值,这意味这什么?
这代表没东西再可以找到这块空间了,我们到时候free§只是free()掉了p后面的一些空间,那么p前面的空间呢?不管了?
所以如果p这个位置改变了,找不到了是一件很可怕的事情。

那么,我们如何避免呢?
如果在后续我们要操作p,我们一开始就要用另一个的指针变量来记录我一开始p的位置,最后free()这个记录起始位置的指针变量,这样就可以避免“free()了,但没完全free()的问题”

对同一块动态开辟的空间,多次释放的问题

//5.对同一块动态开辟的空间,多次释放
int main()
{
int* p = (int*)malloc(100);
//使用
//...
free(p);//万一一个在函数里面,没发现,就会出现这种情况 //
free(p);
return 0;
//所以这个时候,我们需要再free完,立刻把p置为空指针
//这样的话,第二次调用free()的时候,传空指针,什么事情都不会发生
}

看到这里,有些伙伴可能会问:我肯定不会两个free写在一起啊
这里其实只是比较明显而已,如果在函数里面,已经free了,后面又写了很多代码,最后想起来又free一次,这样就会出现这种问题了

为了避免这个问题,我们一定要主动地在free()之后,马上把我们的p置空:p=NULL;这样我们后面就算在free(),free()函数传NULL给它的时候,是不会有任何问题的。

动态开辟的空间忘记释放(比较严重的问题)

//6.动态开辟的空间忘记释放
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
return;
}
//使用
//忘记释放了
//
//
//
//当我们出函数的时候,p会自动释放,那没人可以找到那块在堆上的空间了
//如果return以后,就没有人可以找到p指向的空间了
//这样会造成内存泄露
}
int main()
{
test();
return 0;
}

我们在前面介绍部分释放这个问题的时候已经知道,不free()会造成内存泄漏 在这里,我来为大家总结一下堆上动态开辟的空间的两种释放方式
1.程序员代码主动释放,即调用free()函数
2.程序结束
在我们一开始使用动态内存的阶段,这个问题可能看似没那么严重,程序好像照样跑,反正泄露一点也没关系,程序结束它就自动释放了 但如果我们这是一个服务器的程序呢,24小时跑呢,假如内存只有32个g,每天泄露一点,那机器不就越来越卡了?,最后只会卡死,卡死了就重启,又卡死,再重启。这种问题就会对我们的机器的使用造成很大困扰。所以内存泄漏其实是一个比较严重的问题,所以我们一定要记得free().

柔性数组

虽然柔性数组这个概念在我们的使用中并不常见,但是也是我们要掌握的要点之一
C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫柔性数组成员

struct S
{
int n;
int arr[];//大小是未知的
//int arr[0];也可以
};

此处的arr[]就是一个柔性数组,大小未知。
那我们如何去使用它呢

struct S
{
int n;
int arr[];//大小是未知的
//int arr[0];也可以
};
int main()
{
//期望arr的大小是10个整型
struct S*ps=(struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
ps->n = 10;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
//包含柔性数组的结构,统一用malloc来开辟空间
//增加
struct S*ptr=realloc(ps, sizeof(struct S) + 20 * sizeof(int));
//增加到20个
if (ptr != NULL)
{
ps = ptr;
}
//使用
//......
//释放
free(ps);
ps = NULL;
return 0;
}

注意的点: 1.包含柔性数组的结构体,统一用malloc()来开辟空间 2.开辟空间之前,我们要有一个柔性数组期望的大小,就是我们大概想要用多少,我们是要清楚的

模拟实现柔性数组

我们知道,既然我们在结构体内部定义arr[]的时候,我们是不用定义数组大小的,而与此同时数组名又是首元素地址,那么我们为什么不把结构体里面的int arr[];直接写成int*arr;?
使用这种定义方法,arr已经不在是柔性数组了,但是,为了模拟柔性数组的功能,我们可以让arr的空间在堆上开辟

struct S
{
int n;
int* arr;
};
//我们要保证我们的n,arr指向的空间,都要是在堆上开辟
int main()
{
struct S*ps=(struct S*)malloc(sizeof(struct S));
//先开辟一块给struct S
if (ps == NULL)
{
return 1;
}
//再找一个指针指向arr
//刚才那个指针ps是指向整个结构体S的
//现在搞一个指针指向arr
//有了这个指针arr,我们就可以操作这个数组了
ps->arr =(int*)malloc(10 * sizeof(int));
if (ps->arr == NULL)
{
return 1;
} //使用
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
//增加
int*ptr=realloc(ps->arr, 20 * sizeof(int));
if (ptr != NULL)
{
ps->arr = ptr;
}
//释放的时候一定要先释放arr,再释放ps
//如果先释放ps,那么arr就找不到了
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}

需要注意的点:1.要想实现柔性数组的功能,必须在堆上为arr开辟空间,不能在栈上开辟。 2.我们要使用ps->arr这个指针来操作数组 3.释放空间的时候,一定要先释放arr,再释放ps。为什么?因为ps一旦释放,就没有东西可以找到arr了,会造成内存泄漏
以上两种方式,第一种是柔性数组的运用
第二种不是,只是一种用指针的方式,实现相当于柔性数组的功能
但是再相比之下,第一种,使用柔性数组的方式更加好、
因为第二种有两次malloc(),对应着两次free(),更容易出错
如果频繁使用内存块,会导致效率降低
多次malloc()并不是一件好的事情

尾声

能看到这里的小伙伴,如果你已经完全理解了这篇博客的内容,相信你对动态内存的理解已经提高了一个层次了如果感觉这篇博客对你有帮助的话,别忘了你的赞,收藏和关注哦

【动态内存】C语言动态内存管理及使用总结篇【初学者保姆级福利】的更多相关文章

  1. C语言动态内存管理

    1-概述 动态存储管理的基本问题是:系统如何按请求分配内存,如何回收内存再利用.提出请求的用户可能是系统的一个作业,也可能是程序中的一个变量. 空闲块 未曾分配的地址连续的内存区称为“空闲块”. 占用 ...

  2. C语言动态内存

    动态分配内存的概述 在数组一章中,介绍过数组的长度是预先定义好的,在整个程序中固定不变,但是在实际的编程中,往往会发生这种情况,即所需内存空间取决于实际输入的数据,而无法预先确定.为了解决上述问题,c ...

  3. rt-thread中动态内存分配之小内存管理模块方法的一点理解

    @2019-01-18 [小记] rt-thread中动态内存分配之小内存管理模块方法的一点理解 > 内存初始化后的布局示意 lfree指向内存空闲区首地址 /** * @ingroup Sys ...

  4. oracle结构-内存结构与动态内存管理

    内存结构与动态内存管理 内存是影响数据库性能的重要因素. oracle8i使用静态内存管理,即,SGA内是预先在参数中配置好的,数据库启动时就按这些配置来进行内在分配,oracle10g引入了动态内存 ...

  5. C语言动态内存的申请和释放

    什么是动态内存的申请和释放? 当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量.当不再使用该变量时,也就是它的生命结束时,要显式释放它所占用的存储空 ...

  6. Android JNI编程(五)——C语言的静态内存分配、动态内存分配、动态创建数组

    版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 一:什么是静态内存什么又是动态内存呢? 静态内存:是指在程序开始运行时由编译 ...

  7. JVM内存管理------JAVA语言的内存管理概述

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓 ...

  8. C语言的内存管理

    C语言的内存管理 转载:http://blog.csdn.net/wind19/article/details/5964090   对于一个C语言程序而言,内存空间主要由五个部分组成代码段(.text ...

  9. JVM内存管理之JAVA语言的内存管理概述

    引言 内存管理一直是JAVA语言自豪与骄傲的资本,它让JAVA程序员基本上可以彻底忽略与内存管理相关的细节,只专注于业务逻辑.不过世界上不存在十全十美的好事,在带来了便利的同时,也因此引入了很多令人抓 ...

  10. c语言之内存管理

    在计算机系统,特别是嵌入式系统中,内存资源是非常有限的.尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源.本文是作者在学习C语言内存管理的过程中做的一 ...

随机推荐

  1. 0x53 动态规划-区间DP

    A: 石子合并 所求问题:1到n这些石子合并最少需要多少代价 由于石子合并的顺序可以任意,我们将石子分为两个部分 子问题:1到k这堆石子合并,k+1到n这堆石子合并,再把两堆石子合并,需要多少代价\( ...

  2. 五、mycat水平分库

    系列导航 一.Mycat实战---为什么要用mycat 二.Mycat安装 三.mycat实验数据 四.mycat垂直分库 五.mycat水平分库 六.mycat全局自增 七.mycat-ER分片 一 ...

  3. S3C2440移植uboot之支持NANDFLASH操作

      上一节我们移植了uboot,S3C2440移植uboot之支持NORFLASH.这节我们继续移植,支持NANDFLASH. 目录 编译报错 拷贝s3c2410_nand.c,修改宏定义支持SC32 ...

  4. MySQL 及调优

    存储引擎的种类 MySQL 中存在多种存储引擎,比如: InnoDB 支持事务: 支持外键: 同时支持行锁和表锁. 适用场景:经常更新的表,存在并发读写或者有事务处理的业务场景. MyISAM 支持表 ...

  5. APB Slave设计

    APB Slave位置 实现通过CPU对于APB Slave读写模块进行读写操作 规格说明 不支持反压,即它反馈给APB的pready信号始终为1 不支持错误传输,就是说他反馈给APB总线的PSLVE ...

  6. [转帖]阿里云Redis开发规范(供大家参考)

    一.键值设计 1. key名设计 (1)[建议]: 可读性和可管理性 以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id ugc:video:1 (2)[建议]:简洁性 ...

  7. [转帖]shell编程:shell变量的核心基础知识与实战(二)

    shell编程:shell变量的核心基础知识与实战(二) https://www.cnblogs.com/luoahong/articles/9152039.html Shell 变量类型 变量可以分 ...

  8. [转帖]警惕Oracle数据库性能“隐形杀手”——Direct Path Read

    一. 简介 Oracle 的11g版本正式发布到今天已经10年有余,最新版本也已经到了20c,但是Direct Path Read(直接路径读)导致性能问题的案例仍时有发生,很多12c的用户还是经常遇 ...

  9. [转帖]Harbor:修改默认的172网段

    背景: harbor 默认启动会随机创建 172 网段的ip地址,跟集群规划的网段冲突 Harbor 网段修改步骤 0. 原来Harbor占用的网段 # 网桥名:harbor_harbor [root ...

  10. [转帖]7.5 TiKV 磁盘空间占用与回收常见问题

    https://book.tidb.io/session4/chapter7/compact.html TiKV 作为 TiDB 的存储节点,用户通过 SQL 导入或更改的所有数据都存储在 TiKV. ...