编程之路,从0开始:动态内存管理

        Hello,大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路。

        我们今天来学习C语言中的动态内存管理。

 

目录

1、为什么要有动态内存管理?

2、malloc和free

(1)malloc函数

(2)free函数

3、calloc和realloc

(1)calloc函数

(2)realloc函数

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

(1)对空指针的解引用问题

(2)对动态开辟空间的越界访问

(3)对非动态开辟内存使用free

(4)使用free释放动态内存的一部分

(5)对于同一块动态内存多次释放

(6)动态开辟内存忘记释放

5、柔性数组


 

1、为什么要有动态内存管理?

        首先我们了解一下什么是动态内存管理。简单来说就是C语言支持你自己去开辟和释放内存。比方说我想要40字节的内存,那么我们就可以灵活的开辟这块内存,然后再在这块内存中存放数据。

那么我们直接用常规的方式存储数据不就好了吗?可以是可以,但是有两个缺点:

        1、空间开辟大小是固定的:比方说int他就是4个字节,没法改变。

        2、数组在声明的时候,必须指定数组的长度。数组空间大小确定了就不能调整了。

而动态内存管理的出现,就可以让程序员自己去开辟和释放内存,变得更灵活了。


2、malloc和free

(1)malloc函数

        malloc是一个动态内存开辟函数,其函数原型如下:2d301f9561504e2ab8dadb050a09f57a.png

        这个函数就是用来开辟空间的,其中malloc开辟的空间单位为字节。

如果开辟成功,就返回一个指向这块空间的指针。

如果开辟失败,就返回一个空指针。而null无法被使用。

        这就意味着,我们在开辟内存后一定要检查返回的是否为空指针。

        如果开辟成功,返回的指针类型为void*,所以我们在使用时要先强制转化该指针。

例如,我们现在开辟一块空间并使用它(放入内容):

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(4);if (p != NULL){*p = 5;printf("%d", *p);}free(p);p = NULL;return 0;
}

        以上是一套完整的开辟空间以及使用代码。大家应该都发现了里面出现了free,那么什么是free呢?


(2)free函数

        free函数是用来释放动态内存的。

其函数原型如下:

e9c872a1b602477293af7228ad6357cb.png

        如果参数ptr指向的空间不是动态内存,那么free函数的行为是未定义的。

        如果参数ptr指向空指针,那么函数什么事都不做,

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

        注意:函数在申请内存之后一定要释放内存,否则可能会导致内存泄漏,或者由于重复开辟内存空间导致占用大量内存!


3、calloc和realloc

(1)calloc函数

函数原型:

aceba3c1a5be41e297f1af5368e03162.png

        它可以为num个占用size个字节的元素开辟空间。

        和malloc的区别是,calloc会把申请的空间全都初始化为0。

例如:

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)calloc(5,4);if (p != NULL){int i = 0;for (i = 0;i < 5;i++){printf("%d ", *(p + i));}}free(p);p = NULL;return 0;
}

运行结果:

9a51b6899dad42b788de42b0140ca0cc.png


(2)realloc函数

        这个函数可以让我们调整内存。

函数原型:

8aab8334c95548bcaa7e3fcce40951a6.png

        我们先给定一个申请好动态内存的地址,在传入想到让其扩大后的最终大小。

        返回值为原内存起始位置。

        但需要注意的是,倘若我们无法调整到想要的空间大小,也就是说在原地址的基础上我们没有足够大的空间来扩充,那么该函数会找一个新的地址去开辟这么大的空间,在返回新开辟的位置的起始地址。

验证:

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(1);//开辟1个字节if (p != NULL){printf("%p\n", p);p = (int*)realloc(p, 2);//扩为两个字节printf("%p\n", p);}elsereturn 1;free(p);p = NULL;return 0;
}

输出结果:

43d1ab002f31444baf28de48de02cc1e.png

测试代码2:

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(4);//开辟4个字节if (p != NULL){printf("%p\n", p);p = (int*)realloc(p, 16);//扩为16个字节printf("%p\n", p);}elsereturn 1;free(p);p = NULL;return 0;
}

运行结果:

0e52a248657b4b88b18729cb055f2d71.png

        那么有没有一种可能就是说他在内存中根本找不到这么大的位置来放这块内存呢?

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(4);//开辟4个字节if (p != NULL){printf("%p\n", p);p = (int*)realloc(p, INT_MAX);//扩为最大整形数字printf("%p\n", p);}elsereturn 1;//如果p为NULL直接结束程序。free(p);p = NULL;return 0;
}

运行结果(调整失败):
1e56e2d024e84bb4abe9d3c17a7e9878.png        所以说,在我们使用realloc是要注意一些。


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

(1)对空指针的解引用问题

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(INT_MAX);//开辟4个字节*p = 5;free(p);p = NULL;return 0;
}

我们无法开辟这个大小的内存空间,导致开辟失败返回空指针。程序直接挂掉


(2)对动态开辟空间的越界访问

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(5*sizeof(int));int i = 0;for (i = 0;i < 6;i++){*(p + i) = 1;printf("%d", *(p + i));}free(p);p = NULL;return 0;
}

当i=5时越界访问,程序直接挂掉。


(3)对非动态开辟内存使用free

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = 5;printf("%d", *p);free(p);p = NULL;return 0;
}

没有输出结果。


(4)使用free释放动态内存的一部分

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(5*sizeof(int));p++;free(p);p = NULL;return 0;
}

程序直接挂掉。


(5)对于同一块动态内存多次释放

#include <stdio.h>
#include<stdlib.h>
int main()
{int* p = NULL;p = (int*)malloc(5*sizeof(int));p++;free(p);free(p);p = NULL;return 0;
}

程序直接挂掉。


(6)动态开辟内存忘记释放

        注意,这种情况可能会造成内存泄漏问题,当程序简单一些时,有可能可以正常运行,但是这不代表着可以不释放内存!开辟内存和释放内存一定是成套使用的。


5、柔性数组

        柔性数组就是一种可以让我们灵活调整其空间的数组。

但是需要注意:

        1、结构体中柔性数组成员前面至少包含一个其他成员

        2、sizeof返回这种结构的大小时不包含柔性数组

        3、用malloc调整结构中柔性数组的大小时,调整的最终大小应该大于这个结构体的大小。

现在我们使用以下柔性数组:

#include <stdio.h>
#include<stdlib.h>
struct s1
{int a;int arr[0];//柔性数组我们不填入他的大小
}s;
int main()
{struct s1 *p = (struct s1*)malloc(sizeof(struct s1) + 10 * sizeof(int));//结构体指针名称为p//开辟一块地址,大小为结构s所占字节加上100个整型大小(给柔性数组)if (p != NULL)//还记得吗?我们要判断是不是空指针{int i = 0;for (i = 0;i < 10;i++){p->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素printf("%d ", p->arr[i]);}struct s1* p1 = (struct s1*)realloc(p, sizeof(struct s1) + 15 * sizeof(int));//判断是否为空指针(我们这里省略了)for (i = 10;i < 15;i++){p1->arr[i] = i;//访问结构体指针成员必须用结构体指针名称指向这个元素printf("%d ", p1->arr[i]);}free(p);p = NULL;return 0;}elsereturn 1;
}

        这是一套完整的使用并调整柔性数组大小的代码。

     


   那么我们现在思考一下,如果不用柔性数组,我们可以完成以上操作吗?

#include <stdio.h>
#include<stdlib.h>
struct s1
{int a;int *arr;
}s;
int main()
{struct s1 *p = (struct s1*)malloc(100 *sizeof(int));//这里我们先划定p这个结构体指针变量所在的那一块空间if (p != NULL)//还记得吗?我们要判断是不是空指针{p->arr = (int*)malloc(10 * sizeof(int));int i = 0;for (i = 0;i < 10;i++){p->arr[i] = i;//下标引用操作符的实质是指针printf("%d ", p->arr[i]);}p->arr = (int*)realloc(p,15 * sizeof(int));//调整内存空间for (i = 10;i < 15;i++){p->arr[i] = 1;//下标引用操作符的实质是指针printf("%d ", p->arr[i]);}free(p->arr);//我们开辟了两次内存,所以就要释放两次。p->arr = NULL;free(p);p = NULL;return 0;}elsereturn 1;
}

尽管过程有些许的坎坷,但我们也是完成了这串代码。

以上两串代码的输出结果:

fdf300d53736443881a83e89dff43f49.png

        那么这两种代码写法哪一个更好的?

        答案是柔性数组更好。因为柔性数组方便内存释放,我们只用释放一次就好。

        其次,柔性数组更有利于访问速度,而且我们的代码写起来不也更简单吗?

好了,今天的内容就分享到这,觉得有帮助的老铁点点关注支持一下,我们下次再见!

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/886951.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【题解】AT_joisc2007_mall ショッピングモール (Mall)

原题传送门 温馨提示&#xff1a;岛国题要换行&#xff01; 需要求一个矩阵的和&#xff0c;考虑二维前缀和。 题目中不允许矩阵中有负数&#xff0c;结合求和的最小值&#xff0c;我们把负数赋为最大值不就行了吗。 接下来就是求二维前缀和了。 基于容斥原理&#xff0c;二…

Apifox软件Mock前端数据,帮忙生成API接口文档

Apifox是一款功能强大的接口调试软件&#xff0c;其特色功能丰富&#xff0c;且在前端mock数据生成方面表现出色。以下是对Apifox软件特色功能的详解&#xff0c;以及如何进行前端mock数据生成的步骤&#xff1a; https://apifox.com/help/api-docs/exporting-api https://www…

入门pandas

pandas是本书后续内容的首选库。它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。pandas经常和其它工具一同使用&#xff0c;如数值计算工具NumPy和SciPy&#xff0c;分析库statsmodels和scikit-learn&#xff0c;和数据可视化库matplotlib。pandas是基于NumPy…

Apple Vision Pro开发001-开发配置

一、Vision Pro开发硬件和软件要求 硬件要求软件要求 1、Apple Silicon Mac(M系列芯片的Mac电脑) 2、Apple vision pro-真机调试 XCode15.2及以上&#xff0c;调试开发和打包发布Unity开发者账号&&苹果开发者账号 二 、开启无线调试 1、Apple Vision Pro和Mac连接同…

无人机与低空经济:开启新质生产力的新时代

无人机技术作为低空经济的核心技术之一&#xff0c;正以其独特的优势在多个行业中发挥着重要作用&#xff0c;成为推动新质生产力革命的重要力量。无人机的应用范围广泛&#xff0c;从农业植保到物流配送&#xff0c;从城市监测到紧急救援&#xff0c;无人机的身影无处不在&…

故障排除-------K8s挂载集群外NFS异常

故障排除-------K8s挂载集群外NFS异常 1. 故障现象2. 原因梳理2.1 排查思路2.2 确认yaml内容2.3 创建k8s内的nfs测试2.3.1 创建nfs和svc2.3.2 测试创建pvc2.3.3 测试结果 2.4 NFS服务端故障排除2.4.1 网络阻断排除2.4.2 排除服务状态问题2.4.3 排查NFS权限问题 3. 故障排除 1. …

微服务即时通讯系统的实现(客户端)----(2)

目录 1. 将protobuf引入项目当中2. 前后端交互接口定义2.1 核心PB类2.2 HTTP接口定义2.3 websocket接口定义 3. 核心数据结构和PB之间的转换4. 设计数据中心DataCenter类5. 网络通信5.1 定义NetClient类5.2 引入HTTP5.3 引入websocket 6. 小结7. 搭建测试服务器7.1 创建项目7.2…

凸函数与深度学习调参

问题1&#xff1a;如何区分凸问题和凹问题&#xff1f; 问题2&#xff1a;深度学习如何区分调参&#xff1f;

使用可视化工具kafkatool连接docker的kafka集群,查看消息内容和offset

1、下载kafkatool 下载地址Offset Explorer&#xff0c;下载对应系统的offset explorer 下载完&#xff0c;傻瓜安装即可&#xff08;建议放D盘&#xff09;&#xff0c;在开始菜单输入offset找到该应用打开 打开 2、连接kafka 点击File > add new connection Bootstrap…

关于Java使用ueditor上传图片的一些总结

1.如何配置ueditor让上传的图片到项目之外&#xff1f; 因为图片上传到web项目中,重新部署项目可能会丢失图片。 解决方法&#xff1a;下载ueditor.1.1.2.jar. 地址&#xff1a;ueditor-1.1.2项目源码及jar包.zip 链接: https://pan.baidu.com/s/1Bhumfw8OX16n0MTO9ur73g 提…

React可以做全栈开发吗

React可以做全栈开发吗? 答案是肯定的&#xff0c;而且还比较完美 React可以用于全栈开发&#xff0c;以下是具体的介绍&#xff1a; 前端部分 构建用户界面 React是一个用于构建用户界面的JavaScript库&#xff0c;它通过组件化的方式让开发者能够高效地创建交互式的UI。例…

【前端学习笔记】Javascript学习二(运算符、数组、函数)

一、运算符 运算符&#xff08;operator&#xff09;也被称为操作符&#xff0c;是用于实现赋值、比较和执行算数运算等功能的符号。 JavaScript中常用的运算符有&#xff1a; 算数运算符、递增和递减运算符、比较运算符、逻辑运算符、赋值运算符 算数运算符&#xff1a; 、-…

Redis五大基本类型——List列表命令详解(命令用法详解+思维导图详解)

目录 一、List列表类型介绍 二、常见命令 1、LPUSH 2、LPUSHX 3、RPUSH 4、RPUSHX 5、LRANGE 6、LPOP 7、RPOP 8、LREM 9、LSET 10、LINDEX 11、LINSERT 12、LLEN 13、阻塞版本命令 BLPOP BRPOP 三、命令小结 相关内容&#xff1a; Redis五大基本类型——Ha…

快速入门消息队列MQ、RabbitMQ

目录 一、MQ简介 1.同步调用 2.异步调用 3.技术选型 二、RabbitMQ 1.安装 2.控制台的使用说明 2.1交换机 2.2队列​编辑 2.3绑定关系 3.AMQP 3.1快速入门 3.2WorkQueues模型 3.3交换机 3.3.1 Fanout交换机 3.3.2 Direct交换机 3.3.3 Topic交换机 3.4 声明交换机…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

《现代网络技术》读书笔记:NFV功能

本文部分内容来源于《现代网络技术&#xff1a;SDN,NFV,QoE、物联网和云计算&#xff1a;SDN,NFV,QoE,IoT,andcloud》 NFV基础设施 NFV体系结构的核心是资源与功能集合&#xff0c;也为称为NFV基础设施(NFVI)。NFVI包括以下三个域&#xff1a; 计算域&#xff1a;提供商用的大…

MySQL数据库2——SQL语句

一.SQL基础 1.SQL通用语法 1.SQL语句可以单行或多行书写&#xff0c;以分号结尾。2.SOL语句可以使用空格/缩进来增强语句的可读性。3.MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写 注释&#xff1a; 单行注释&#xff1a;-- 注释内容或#注释内容(MySQL…

会员等级经验问题

问题描述 会员从一级完成任务升级到二级以后&#xff0c;一级显示还差经验&#xff0c;这里差的其实是二级到三级的经验&#xff0c;如下图所示 修复方法 1、前端需要修改&#xff1a; 路径&#xff1a;/pages/users/user_vip/index.vue 方便复制&#xff1a; v-if"i…

【Apache Paimon】-- 6 -- 清理过期数据

目录 1、简要介绍 2、操作方式和步骤 2.1、调整快照文件过期时间 2.2、设置分区过期时间 2.2.1、举例1 2.2.2、举例2 2.3、清理废弃文件 3、参考 1、简要介绍 清理 paimon &#xff08;表&#xff09;过期数据可以释放存储空间&#xff0c;优化资源利用并提升系统运行效…

Spring Boot整合Kafka,实现单条消费和批量消费,示例教程

如何安装Kafka&#xff0c;可以参考docker搭载Kafka集群&#xff0c;一个文件搞定&#xff0c;超简单&#xff0c;亲试可行-CSDN博客 1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sta…