动态内容管理

news2024/11/15 11:10:27

这期我们来看动态内存管理的相关知识,话不多说,我们来看今天的正题

目录

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

2.动态内存函数的介绍

2.1.malloc和free

2.2.calloc

2.3.realloc

 3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

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

3.3 对非动态开辟内存使用free释放

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

3.5 对同一块动态内存多次释放

 3.6 动态开辟内存忘记释放

4.C/C++程序的内存开辟

5.柔性数组

5.1柔性数组特点

5.2柔性数组的优点


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

在我们平时写代码时,我们创建的变量都会开辟空间,比如我们创建一个int类型的数据,要开辟4字节的空间,创建一个int arr[10],需要开辟40个空间,这样开辟空间有两个特点

1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配

对于一些问题,我们传统的创建变量是无法解决的,比如我们的通讯录,可以存储100个人的信息,但对于不同的人有不同的需求,有人可能需要存储200个人的信息,有人可能只需要存几个,对于需求多的人数量不够,对于需求少的人空间又会浪费,这时,我们就可以使用动态内存管理来解决问题

2.动态内存函数的介绍

我们知道我们的局部变量,函数的形式参数等等都是在栈区开辟空间,而栈区的空间有时候是不够用的,我们的内存一般被分为3个区域,栈区,堆区,静态区(全局变量就在静态区),堆区的空间非常大,而且可以被我们使用,我们接下来介绍的四个函数,就是作用于堆区的

2.1.malloc和free

 如同它介绍的一样,开辟内存空间,从堆内存上申请size大的空间,返回一个指向这块空间起始位置的地址,这里我们只是申请了这么大的空间,但里面要存放什么内容,int还是double之类我们并不知道,所以返回类型为void*

int* p = (int*)malloc(40);

这就是我们申请了一块空间大小为40个字节的代码,我们要申请空间来使用,我们自己不可能不知道这块空间要放什么内容,比如我想存放int类型数据,所以接收的指针为int*,又因为malloc返回类型为void*,两边类型不同,所以需要强制类型转换,既然是我们申请空间,就有可能会申请失败,申请失败会返回一个空指针

 这是我们使用动态内存管理来实现打印出1到10的一个例子,但是,这样写是有一个错误的,我们用malloc从堆区申请了空间,就像我们去图书馆借书一样,有借就要有还,如果不还,其实在这个程序结束时也会将我们申请的空间还给操作系统,但是如果这个程序一直不结束(比如服务器),我们又不还,那这块空间就被浪费掉了

要归还空间,就要使用free函数

我们只需把指向我们申请空间的指针传给free就可以释放空间

 在我们释放空间后,其实还没有结束,我们释放空间后,p指针还记录着我们申请的那块空间的地址,如果有人通过p去访问空间,就会形成非法访问,free并不会主动将指针置为空,所以需要我们手动来置空指针

 接着我们来看一个申请失败的例子

INT_MAX是一个21亿多的数字,所以会申请失败,我们看strerror给我们返回的错误就是没有那么大的空间

我们在开辟空间时,如果传入的参数size为0,即malloc(0),这种情况是c语言标准未定义的,结果取决于编译器

 同样的,free的参数得是我们动态开辟的,如果不是动态开辟的空间,那free函数的行为也是未定义的,同时,如果传入的参数为NULL,则函数什么事都不会做

2.2.calloc

calloc也是用来动态开辟空间的, 但它的参数和malloc是有差异的,num是元素个数,size是每个元素的大小,相当于把malloc的参数细致化,我们来看个例子

 我们同样要判断申请空间是否为空,这里我们使用perror即可,我们发现,这次我们并没有对空间里的内容进行初始化,但打印出的元素都为0,那malloc呢?

我们发现malloc并没有对数据进行初始化,而calloc会对数据进行初始化,所以我们使用时需要注意,如果我们不希望这块空间被初始化时可以使用malloc,希望被初始化可以使用calloc,我们可以想象到,malloc因为没有进行初始化,所以效率其实更高一些,当然,我们要根据自己的实际情况选择,使用哪一个都行

2.3.realloc

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整。

 ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

int main() {
	int* p = (int*)calloc(5, sizeof(int));
	if (p == NULL) {
		perror("calloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++) {
		*(p + i) = i;
	}
	//空间不够,增加5个整形空间
	int* ptr = (int*)realloc(p, 10 * sizeof(int));
	return 0;
}

realloc申请是有两种情况的

情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些

在情况1时,在原空间后接上后续新空间,然后会返回旧的起始地址

在情况2时,在申请完新空间后,会把旧空间的数据拷贝到新空间前面的位置,并且会把旧空间释放掉,同时返回新空间的地址

realloc扩容失败会返回NULL,所以我们使用realloc时不能使用旧的指针来接收空间,万一开辟失败,我们的旧指针就指向NULL了,我们原来的数据就找不到了

所以如果我们还想使用旧指针的话可以这样写,如果之后不会再使用ptr的话,再加上将ptr置为空即可

另外,无论是哪种情况,后续空间的元素并不会初始化 

另外,我们在给realloc传参时,如果第一个参数为空指针,realloc的功能就相当于malloc

 3. 常见的动态内存错误

3.1 对NULL指针的解引用操作

int main() {
	int* p = (int*)malloc(100);
	int i = 0;
	for (i = 0; i < 20; i++) {
		*(p + i) = 0;
	}
	return 0;
}

我们在申请完空间后,一定要记得判断空间是否申请成功,如果申请失败,后续操作就会出现非法访问的问题

我们编译后用鼠标指上去会发现编译都在警告 

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

int main() {
	int* p = (int*)malloc(100);
	if (p == NULL) {
		return 1;
	}
	int i = 0;
	for (i = 0; i < 100; i++) {
		*(p + i) = 0;
	}
	return 0;
}

如果我们稀里糊涂的将100个字节空间当做了100个整形,就像上面代码这样,就形成了越界访问

编译器也会发出警告

3.3 对非动态开辟内存使用free释放

int main() {
	int a = 10;
	int* p = &a;
	free(p);
	p = NULL;
}

我们写代码时有可能写出成百上千行的代码,变量太多时我们可以忘记了某些变量是什么,就会出现这种情况,当我们这样运行时,程序不出意外的挂掉了

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

int main() {
	int* p = (int*)malloc(100);
	if (p == NULL) {
		return 1;
	}
	int i = 0;
	for (i = 0; i < 25; i++) {
		*p = i;
		p++;
	}
	free(p);
    p=NULL;
	return 0;
}

当我们写出这样的代码时,会发现p指针释放时已经不再指向我们申请内存时的起始位置了,我们释放时一定要传起始地址才行,当程序运行时,代码也会挂掉

3.5 对同一块动态内存多次释放

int main() {
	int* p = (int*)malloc(100);
	if (p == NULL) {
		return 1;
	}
	//使用...
	//释放
	free(p);
	//...
	free(p);
	return 0;
}

当我们代码写多时,可能忘记前面已经释放过空间了,但我们再释放一次,程序就会出现问题

同样的,编译器也会发出警告 ,执行代码的话程序也会挂掉

我们第一次释放空间后,p就相当于野指针了,再次释放肯定会出现问题

所以我们释放完空间后一定要记住将指针置为空,这些操作都是有意义的 

 3.6 动态开辟内存忘记释放

int* test() {
	int* p = (int*)malloc(100);
	if (p == NULL) {
		return 1;
	}
	//使用
	return p;
}
int main() {
	int* ptr = test();
	//...
	return 0;
}

我们用一个函数来开辟空间,然后把开辟好的空间传给一个指针时,使用完后一定要记得释放空间,不然会造成内存泄漏,对于上面的test函数,我们一定要写好注释,告诉别人这个函数使用了malloc函数,你在使用这个函数后要记得释放空间才行

 比如某些公司的服务器,隔三岔五就会出现问题,一重启就正常了,此时就可能会有这种问题,因为程序在不停的吃内存

4.C/C++程序的内存开辟

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

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

 有了这幅图,我们就可以更好的理解static关键字修饰局部变量的例子了
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序
结束才销毁所以生命周期变长

5.柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员

struct S {
	int n;
	char c;
	char arr[];
};

就像这样,我们定义一个结构体,最后一个元素为数组,数组大小可以不指定,或者写成arr[0],两种写法会支持一种(不同编译器),或都可以使用,这个数组就叫柔性数组成员

5.1柔性数组特点

结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

 第一个特点意思是,我们的结构体要使用柔性数组至少要有两个成员变量才行,我们来看个错误例子

struct S {
	char arr[];
};

这个结构体只有柔性数组,是错误的,前面必须有一个元素

第二个特点的意思是我们计算结构体大小时不会计算柔性数组,比如

 第三个特点也给我们说明了柔性数组如何创建变量

struct S {
	int n;
	char arr[];
};

int main() {
	struct S s;
	struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(char) * 10);
}

如果我们想让这个数组可以存放10个元素,就如上面代码所示,我们应该用malloc来申请空间,申请空间大小为原结构体大小加上我们想让数组多多少元素大小,如果是int数组就要乘以40,然后依照malloc的使用方法,我们用该结构体的指针来接收,后期我们觉得数组不够用,可以进行调整大小,所以这个数组叫做柔性数组

使用时按照常规数组使用即可,我们看扩容例子

 另外,因为是申请的空间,所以别忘记释放 

 

5.2柔性数组的优点

可能会有人疑惑,柔性数组有什么意义呢?我们可以用别的方式也能实现这样的结构,比如

用malloc也可以给arr分配空间,这样做和柔性数组区别是什么呢? 

这样做时,s里边的元素,n和arr是在一块空间的,但arr所指向的空间,是另一块独立的空间,而柔性数组,整个空间都是在一起的,空间是连续的,是有好处的,有一个概念叫做空间碎片

假如两个大方块是内存空间,小方块是我们malloc申请的,我们多次malloc时,内存碎片会很多,而我们malloc少,一次开辟大一点的话,内存碎片就很少,利用率自然高,而且,空间连续时,我们访问的效率也更高

另外,在释放空间时,我们必须先释放arr所指向的空间,然后才能释放s,如果是柔性数组,就可以直接释放s,我们在代码写的很多时,malloc和free这些操作少一点更好,毕竟我们有时是会忘记的

以上就是本期的全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

Pytorch DataLoader中的num_workers (选择最合适的num_workers值)

一、概念 num_workers是Dataloader的概念&#xff0c;默认值是0。是告诉DataLoader实例要使用多少个子进程进行数据加载(和CPU有关&#xff0c;和GPU无关) 如果num_worker设为0&#xff0c;意味着每一轮迭代时&#xff0c;dataloader不再有自主加载数据到RAM这一步骤&#xff0…

滑动列表中使用粒子特效层级问题

前言 前面几个月疯狂堆功能,现在开始疯狂加动效,每次一说到动效就脑壳痛,还不如让我写功能。这不,今天又遇到问题了。滑动列表中mask粒子特效问题遮挡。 情况1 步骤1:使用粒子特效的层级应该>当前ui层级。 例如:当前界面所在层级为2000,其上的粒子特效至少为2001。…

dp(八)买卖股票的最好时机 (一,二、三)

目录 买卖股票的最好时机(一)_牛客题霸_牛客网 买卖股票的最好时机(二)_牛客题霸_牛客网 买卖股票的最好时机(三)_牛客题霸_牛客网 假设你有一个数组prices&#xff0c;长度为n&#xff0c;其中prices[i]是股票在第i天的价格&#xff0c;请根据这个价格数组&#xff0c;返回买…

基于云的文档管理系统:DocuWare Cloud

云文档管理软件&#xff1a;DocuWare Cloud 一流的云文档管理软件和工作流自动化内容服务&#xff0c;适用于任何规模的团队和公司——在多租户云平台上交付。 DocuWare Cloud 可在订阅的基础上为不同规模的公司提供灵活的许可证。 每个订阅都涵盖全方位的服务&#xff0c;包…

dvwa中的爆破

环境&#xff1a;dvwa: 192.168.11.135 dvwa版本&#xff1a; Version 1.9 (Release date: 2015-09-19)kail机器&#xff1a;192.168.11.1561、Low级别代码&#xff1a;1、启动 burpsuite 开始抓包&#xff0c;然后点击 login&#xff0c;然后在 bp 里面就能看见抓包到的包。这…

Java集合常见面试题(二)

Collection 子接口之 List ArrayList 和 Vector 的区别? ArrayList 是 List 的主要实现类&#xff0c;底层使用 Object[]存储&#xff0c;适用于频繁的查找工作&#xff0c;线程不安全 &#xff1b;Vector 是 List 的古老实现类&#xff0c;底层使用Object[] 存储&#xff0…

谷粒学院复习

一、Mybatis Plus复习分布式系统唯一ID主键策略(面试)面试的时候就说知道有以下四种策略&#xff0c;分别介绍一下每一种&#xff0c;然后说一下项目中用的是雪花算法分类自动增长 AUTO INCREMENT就是自动增长&#xff0c;每次都会自动加一。缺点&#xff1a;如果在分库分表的场…

VUE: Vue3+TS的项目搭建及基础使用

简介 通过 Vue-cli4 创建的 Vue3TS 的项目&#xff0c;并进行一些基础使用的举例。 项目搭建 1. 进入命令提示符窗口 在要搭建项目的文件夹中&#xff0c;点击路径&#xff0c;输入CMD并按回车 2. 查看node版本、Vue-cli版本 2.1 node版本&#xff08;14.x以上&#xf…

基于域控的SSO单点登录

大家好&#xff0c;好久不见&#xff0c;今天老吕给大家来一篇偏冷门知识的文章。一、需求大型集团企业内部会有许多业务系统&#xff0c;工作人员也往往需要登录多个业务系统才能完成工作&#xff0c;这就可能会存在一些问题1、多套账号与密码需要记录或者记忆2、多次登录&…

14.live555mediaserver-setup请求与响应

live555工程代码路径 live555工程在我的gitee下&#xff08;doc下有思维导图、drawio图&#xff09;&#xff1a;https://gitee.com/lure_ai/live555/tree/master 学习demo live555mediaserver.cpp 学习线索和姿势 1.学习的线索和姿势 网络编程 流媒体的地基是网络编程&…

Git 的常用命令

Git 的常用命令 目录Git 的常用命令帮助初始化配置提交远程仓库管理版本控制删除分支管理查看文件提交、状态帮助 查看常用命令 git help查看某个命令的使用帮助 git help [命令]查看 git 使用指南&#xff08;这个命令会详细展示 git 的使用周期&#xff09; git help tut…

【BP靶场portswigger-客户端13】跨来源资源共享(CORS)-4个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

2022.12 青少年机器人技术等级考试理论综合试卷(三级)

2022年12月 青少年机器人技术等级考试理论综合试卷&#xff08;三级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 30 一、 单选题(共 20 题&#xff0c; 共 80 分) 1.舵机接到 Arduino UNO/Nano 主控板的 2 号引脚&#xff0c; 下列选项中&#xff0c; 实现舵机在 0 度…

4、字符串处理

目录 一、字符串的构造 二、字符串比较 三、字符串查找 四、字符串替换 五、字符串——数值转换 Matlab中的字符串函数有&#xff1a; 一、字符串的构造 字符串或字符串数组的构造可以通过直接给变量赋值来实现&#xff0c;具体表达式中字符串的内容需要写在单引号内。如…

ESP8266 ArduinoIDE 搭建web服务器与客户端开发

一、wifi 相关配置 1.1 无线终端 wifi 模式 此模式中&#xff0c;esp8266 会连接到指定 wifi 进行工作。 #include <ESP8266WiFi.h> // 本程序使用ESP8266WiFi库const char* ssid "home"; // 连接WiFi名&#xff08;此处使用home为示例&…

Vue2-Vue开发环境搭建

一、IDE编辑器&#xff1a;Vscode&#xff0c;自行下载安装即可 二、三种引入方式&#xff0c; 教程使用方式一引入 Vue官网&#xff1a;https://v2.cn.vuejs.org/v2/guide/installation.html 方式一&#xff1a;直接script引入 教程下载开发版本&#xff0c;下载到本地&…

使用人工智能机器人提高农业效率| 数据标注

人工智能技术创新不仅仅蔓延到智慧城市、智能建筑或新的混合工作模式&#xff1b;机器人还通过人工智能、自动拖拉机、实时监测农作物的传感器、无人机或水果和蔬菜收获机器人来彻底改变农业。今天&#xff0c;我们将向您介绍一些已经在农业中使用的最有趣的AI技术&#xff0c;…

微信小程序textarea的placeholder的行高怎么修改

目前不支持修改行高。如果你的内容设置了行高但是placeholder没有行高会导致输入内容的时候感觉不是对齐的&#xff0c;想要解决这个问题怎么办呢/ 我们可以自己写个text假装是placeholder的内容。然后textarea获取焦点输入内容的时候就不显示这个text的内容。 <view class…

新入公司 git基本命令使用(二) 小乌龟版

git命令行的操作复杂不直观,且容易出错. 这里推荐大家使用 git版小乌龟插件进行使用 下载地址 :https://tortoisegit.org/download/ 安装一路next即可 创建本地仓库 右键点击克隆, 然后输入项目地址,确认 拉取代码 右键点击同步 , 然后再界面中选择好对应的分支, 点击拉取 …

朴素贝叶斯分类算法和实例演示

文章目录贝叶斯公式算法原理实例演示代码实现本文开始&#xff0c;我们来学习一种新的机器学习方法&#xff1a;贝叶斯算法。 这次从最基础的朴素贝叶斯分类算法出发&#xff0c;了解相关的算法原理。 考虑如下一种分类问题&#xff1a;样本中只包含2类特征&#xff0c;标签只…