c语言进阶篇_动态内存管理(数组可以自动扩容?)

news2025/1/12 16:05:59

在这里插入图片描述

前言

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏: 🍔🍟🌯 c语言初阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解c语言中的动态内存管理知识,涉及malloc函数、calloc函数和realloc函数以及柔性数组的知识
金句分享:
✨追风赶月莫停留,平芜尽处是春山!✨

目录

  • 前言
  • 一、动态内存管理是什么?
  • 二、内存操作函数
    • 2.1 malloc函数与free函数
      • malloc
      • free
    • 2.2 calloc函数
    • malloc与calloc的区别:
    • 2.3 realloc函数
  • 三、动态内存函数操作不当造成的错误:
      • (1)访问空指针
      • (2)对同一块空间就行多次释放:
      • (3)向释放申请空间的一部分:
      • (4)内存泄漏(重点):
  • 四、柔性数组与变长数组.
    • 什么是柔性数组?
    • 变长数组:

一、动态内存管理是什么?

如果我们需要创建一个变量,可以直接通过类型名+变量名创建即可.此时会自动向内存申请该类型所需要的的字节空间,例如:int a=0;

该语句会自动向内存申请四个字节的空间(64位机器下),那么如果我们需要多个变量呢?

很显然,在之前,我们就学过数组,数组可以解决创建多个变量的问题,但是,即使是数组也存在一个缺陷.
那就是在创建数组时,我们必须要先确定数组的大小,这样操作系统才会去向内存申请固定大小的字节空间.
而在很多情况下,我们并不能确定要存储的变量个数,这是很常见的问题,
🌰例如:

外卖平台并不能提前知道今天的订单量,淘宝商家也一样不能预测今天商品的销售量等等.

包括我们之前讲解的通讯录简易版我们并不能事先知道要添加的联系人个数,此时用数组去存储,很难确定开多大的数组,开大了浪费,开小了不够用.

   为了解决这个尴尬的问题,c语言提供了一些可以申请内存空间的函数,这些函数被称为动态内存函数.malloc函数,calloc函数以及realloc函数.

二、内存操作函数

2.1 malloc函数与free函数

malloc

文档查询链接:
函数原型:

在这里插入图片描述

参数介绍:

参数意义
size要申请的字节个数(记住单位是字节)

函数作用:

   malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果申请成功:则返回一个指向开辟好空间的指针。
如果申请失败:则返回一个NULL指针,所以我们在使用malloc函数申请空间时,要判断返回值是否为空,空指针则代表申请失败。当然这种情况是很少发生的,但是作为一名合格的程序员,还是建议加上返回值的判断,这也是对程序员自己的帮助.

返回值解释:
返回值的类型是 void* ,因为我们在使用malloc函数申请空间时可以给多种类型赋值,不能限制返回值的类型,在具体使用时,强制转换为需要的类型即可.
示例:

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	int* a = (int*)malloc(10 * sizeof(int));
	if (a == NULL)
	{
		perror("malloc a fail");//申请失败时,打印错误信息
		return 0;
	}

	//向内存申请2个双精度形所占的字节个数的空间,通过强转为int*后赋值给b
	double* b = (int*)malloc(2 * sizeof(double));
	if (b == NULL)
	{
		perror("malloc b fail");//申请失败时,打印错误信息
		return 0;
	}
	return 0;
}

例图:
在这里插入图片描述

还有人很调皮,将size设置为0,malloc(0);这就让编译器很无奈,这种行为是未定义的,0就是不申请空间吗?
不申请你找我(malloc)干嘛?咱还是规规矩矩的写代码,做一个乖孩子吧.
在这里插入图片描述

free

我们在介绍free函数之前,先简单介绍一下内存部分分区吧!

栈区:
用于存放局部变量,函数参数等临时变量.

堆区:(今天的重点)
是用于供程序员申请的内存区,malloc函数,calloc函数和realloc函数就是在这里申请内存空间.

静态区:
用于存放全局变量和静态变量.

当我们在自定义一个函数时,会在栈区上开辟一块空间给该函数,当函数调用结束,为函数开辟的空间就会被收回,则其中的变量也会被销毁.但是malloc函数申请的空间不会,因为它是在堆区上申请的空间,需要申请者自己去释放,而这项操作就需要使用函数free.

函数模型:
在这里插入图片描述

free函数只用来释放动态开辟的内存即用malloc、calloc以及realloc开辟的空间。

参数说明:

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。同样还是那句话,不和规则的事咱就不要做了.
如果参数 ptr 是NULL指针,则该函数不会进行任何操作.

说了这么多,我们实践操作一下吧!

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)malloc(10 * sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	if (a == NULL)
	{
		perror("malloc is fail");//申请失败时,打印错误信息
		return 0;
	}
	//赋值
	for (int i = 0; i < 10; i++)
	{
		a[i] = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	free(a);
	a = NULL;
	return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

在为赋值之前,我们观察一下a空间中存放的值,明显是一些未初始化而产生的的"随机值".
赋值前:

赋值后:

注意:
free(a)后,a指针所指向的内存空间就被释放掉了,后续就不能使用了,则应当为了防止出现空指针,则需要进行"置空"操作.a = NULL;

2.2 calloc函数

函数模型:
在这里插入图片描述
参数说明:

num:要申请的元素个数.
size:一个元素所占的内存大小.

将上面的malloc代码改成calloc函数后:

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)calloc(10 , sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	if (a == NULL)
	{
		perror("calloc is fail");//申请失败时,打印错误信息
		return 0;
	}
	//赋值
	for (int i = 0; i < 10; i++)
	{
		a[i] = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	free(a);
	a = NULL;
	return 0;
}

该函数的重点是能理解与malloc函数区别.

赋值前:

赋值后:

malloc与calloc的区别:

很明显,相比于malloc函数,在申请空间成功后,calloc函数会将申请到的空间全部初始化为0.这里可能有人要问,为啥我们不直接用calloc函数,还需要malloc函数干嘛?
原因是malloc是只需要将空间申请下来就行,而calloc函数还需要清理空间(都初始化为0),这样calloc函数的执行效率就没有malloc快.在很多情况下,我们并不需要初始化为0,这时候直接使用mallo函数就行,效率会高一些.

总结:

malloccalloc
申请到的空间未被初始化申请到的空间全部被初始化为0.
执行效率相对较高执行效率相对较低

2.3 realloc函数

函数原型:

在这里插入图片描述
参数说明:

参数意义
ptr需要重新分配内存空间的地址
size重新分配后内存空间的大小

函数功能:

realloc函数就是为了使得动态内存函数更加配得上"动态"之词的函数.
回到之前的问题,有时会我们发现过去申请的空间太小了,有时候我们又会发现申请的空间过大了导致内存浪费,那为了合理的申请内存.我们需要对内存的大小做灵活的调整。

realloc函数就是重新分配之前开辟的空间大小.

返回值:

返回值为调整之后的内存起始位置。

很重要!!!
这时有两种情况:
①:原地扩容:
原地址后面有足够的空间支持扩容.这时,会占用后面未被分配的内存空间用于扩容.
②:异地扩容:
原地址后面的内存空间不够支持扩容,则需要找到另外一块内存空间,将数据拷贝过去,然后再扩容.返回新的地址.

图解:
在这里插入图片描述

三、动态内存函数操作不当造成的错误:

(1)访问空指针

对申请的空间忘记进行NULL指针判断,导致访问空指针
这里一次申请大量的内存空间,内存没有那么多,会申请失败,返回NULL指针.

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)malloc(100000000000000*sizeof(int));
	//赋值
	for (int i = 0; i < 10; i++)
	{
		a[i] = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	free(a);
	a = NULL;
	return 0;
}

运行结果:
在这里插入图片描述

(2)对同一块空间就行多次释放:

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	if (a == NULL)
	{
		perror("malloc a fail");//申请失败时,打印错误信息
		return 0;
	}
	free(a);
	//中间含有大量代码
	free(a);//导致忘记已经释放过了
	a = NULL;
	return 0;
}

(3)向释放申请空间的一部分:

申请的空间不能释放其中的一部分,只能一次全部释放.

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	if (a == NULL)
	{
		perror("malloc a fail");//申请失败时,打印错误信息
		return 0;
	}
	int* b = a + 3;
	free(b);
	b = NULL;
	return 0;
}

(4)内存泄漏(重点):

忘记释放在堆区上释放的空间

#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
	int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
	if (a == NULL)
	{
		perror("malloc a fail");//申请失败时,打印错误信息
		return 0;
	}
	//赋值
	for (int i = 0; i < 10; i++)
	{
		a[i] = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	return 0;
}

此时,程序并不会报错,但是这时会出现一个很严重的问题,那就是内存泄漏,用malloc函数申请的空间并没有被释放,导致一直占用内存空间.
当然,在程序结束时,系统会自动回收这些未被释放的空间,但是对于一些大型的程序或者在特定情况下,这是非常可怕的.

例如:
1)如果内存泄漏发生在手机上,一次泄漏一点点,手机长期不关机,几天或者几个星期之后,运行内存都被挤满了,会导致手机特别卡.

2)大型服务器是开机后,除了维修或者老化被替代,都是一直不关机的,此时内存泄漏是很可怕的,造成的损失也特别严重.

四、柔性数组与变长数组.

什么是柔性数组?

可能有人在此之前并没有听过柔性数组这个词.
柔性数组表示,在进行定义结构体类型时,结构体的最后一个成员可以是一个不指定大小的数组,这个数组就被称为柔性数组.
例如:

typedef struct test
{
	char name[10];
	int data[];//柔性数组
	//也可以写成int data[0];
}test_struct;

柔性数组的规则:

1.柔性数组前面至少要有一个成员变量,且柔性数组是最后一个成员.
2.在用sizeof对结构体进行计算时,不会计算柔性数组的大小.
3.柔性数组不能直接使用,需要malloc函数进行分配时分配,且分配的大小必须比不计算柔性数组所占的空间要大,要给柔性数组预留空间.

#include <stdio.h>
#include <stdlib.h>
//定义一个包含柔性数组的结构体
typedef struct test
{
	char name[10];
	int data[];
	//也可以写成int data[0];
}test_struct;
int main()
{
	//创建一个结构体指针,并未柔性数组分配空间
	test_struct* test1 = (test_struct*)malloc(sizeof(test_struct)+ 10 * sizeof(int));
	//sizeof(test_struct)表示不计算柔性数组时结构体所占内存大小.
	//10 * sizeof(int)表示在此基础上再增加10个整形的字节空间,会分配给柔性数组.
	//初始化柔性数组
	for (int i = 0; i < 10; i++)
	{
		test1->data[i] = i;
	}
	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", test1->data[i]);
	}
	return 0;
}

柔性数组的优点:
1.由于是连续的内存空间,所以释放时可以一次性释放,不需要分两次释放.
2.同样是因为连续的空间,访问速度较于不连续空间速度更快,因为寄存器一次性读取数据是按连续内存读取的,不连续则需要读取多次.

变长数组:

在c99标准中支持可以用变量来定义数组的大小.
即:

int main()
{
	int a = 10;
	int arr[a];
	return 0;
}

c语言的动态内存管理就讲到这里了,886.
创作不易,有帮助记得三连哦,有错误也可以和博主私信交流!!!
💗💗💗
在这里插入图片描述

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

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

相关文章

微信小程序运行及更新机制

微信小程序运行及更新机制1、微信小程序运行机制1.1 前台和后台1.2 小程序启动&#xff1a;冷启动和热启动1.3 小程序销毁2、微信小程序更新机制2.1 启动时同步更新定期检查发现版本更新用户长时间未使用小程序2.2 启动时异步更新开发者手动触发更新2.3 小程序管理后台的相关设…

软件测试分类详解

一图看清软件测试分类 一、按测试技术分&#xff08;是否查看代码&#xff09; **1. 黑盒测试**&#xff1a;软件功能是否正常使用【功能的测试】 **2. 白盒测试**&#xff1a;代码逻辑是否正确【结构的测试】 **3. 灰盒测试**&#xff1a;介于两者之间的测试&#xff0c;也…

第12章_集合框架

第12章_集合框架 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 本章专题与脉络 1. 集合框架概述 1.1 生活中的容器 1.2 数组的特点与弊端 一方面&#xff0c;面向对象语言对事物的体现都是以对…

iOS 项目嵌入Flutter 运行

一 创建Flutter 模块命令行flutter create --template module my_flutter创建完成后&#xff0c;该模块和普通的Flutter项目一直&#xff0c;可以通过Android Studio或VSCode打开、开发、运行&#xff1b;和之前项目不同的iOS和Android项目是一个隐藏文件&#xff0c;并且我们…

黑马点评缓存练习题shop-type缓存,附带详细解析

黑马点评缓存练习题shop-type缓存 依照shop详情的缓存 Controller代码 public class ShopTypeController {Resourceprivate IShopTypeService typeService;GetMapping("list")public Result queryTypeList() {return typeService.queryList();} }创建service接口方…

SentenceTransformers介绍

SentenceTransformer使用范例 1使用SentenceTransformers获得句子向量嵌入 from sentence_transformers import SentenceTransformer#模型下载 model SentenceTransformer(paraphrase-MiniLM-L6-v2)# 编码句子 sentences [Python is an interpreted high-level general-pur…

JavaScript【五】JavaScript中的对象

文章目录&#x1f31f;前言&#x1f31f;对象&#xff1a;&#x1f31f;声明对象&#xff1a;&#x1f31f;隐式创建对象&#xff1a;&#x1f31f;实例化Object&#xff1a;&#x1f31f;实例化自定义构造函数&#xff1a;(会重复占用内存)&#x1f31f;new运算符具体做了什么…

自动内存管理之【常量池】

首先上一段代码&#xff0c;一起思考&#xff0c;打印的结果&#xff0c;基于jdk1.8。 StringBuilder sbnew StringBuilder("我爱我媳妇儿");String s sb.toString();System.out.println(s.intern()s); //falsesb.append("&#xff0c;她也很爱我&#xff01;&…

C++——入门讲解

作者&#xff1a;几冬雪来 时间&#xff1a;2023年4月16日 内容&#xff1a;C入门讲解 目录 前言&#xff1a; 1.什么是C&#xff1a; 2.C关键字&#xff1a; 3.命名冲突&#xff1a; 4.域和::操作符&#xff1a; 5.std内容讲解&#xff1a; 6.<<符&#xff1a…

STM32F4_独立看门狗详解(IWDG)

目录 1. 独立看门狗是什么 2. 独立看门狗 IWDG简介 3. 独立看门狗的主要特性 4. 独立看门狗功能 4.1 独立看门狗功能框图 4.2 IWDG寄存器 4.2.1 关键字寄存器 IWDG_KR 4.2.2 预分频器寄存器 IWDG_PR 4.2.3 重载寄存器 IWDG_RLR 4.2.4 状态寄存器 IWDG_SR 5. 库函数…

OpenCV实战之人脸美颜美型(六)——磨皮

1.需求分析 有个词叫做“肤若凝脂”,直译为皮肤像凝固的油脂,形容皮肤洁白且光润,这是对美女的一种通用评价。实际生活中我们的皮肤多少会有一些毛孔、斑点等表现,在观感上与上述的“光润感”相反,因此磨皮也成为美颜算法中的一项基础且重要的功能。让皮肤变得更加光润,就…

interface陷阱

A1 interface Duck Typing: 当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子&#xff0c;那么这只鸟就可以被称为鸭子 目的: 在 Go 中&#xff0c;Interface&#xff08;接口&#xff09;只是一组方法集合。描述事物的外部行为而非内部结构。 通过接口实现多态的概…

微服务架构——SpringCloud快速入门

认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 单体架构 将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署。 优点&#…

如何保证缓存和数据库的数据一致性

文章目录1、错误的解决方案1.1、 先更新数据库&#xff0c;再删除缓存1.2、 先更新数据库&#xff0c;再更新缓存1.3、 先删除缓存&#xff0c;再更新数据库1.4、 先更新缓存&#xff0c;再更新数据库2、正确的解决方案2.1、使用 CAS2.2、使用分布式锁2.3、使用消息队列异步更新…

字符串匹配—KMP算法

字符串匹配的应用非常广泛&#xff0c;例如在搜索引擎中&#xff0c;我们通过键入一些关键字就可以得到相关的搜索结果&#xff0c;搜索引擎在这个过程中就使用字符串匹配算法&#xff0c;它通过在资源中匹配关键字&#xff0c;最后给出符合条件的搜索结果。并且我们在使用计算…

SpringBoot解决用户重复提交订单(方式三:通过Redis实现-升级版)

文章目录前言1、方案实践1.1、引入Redis依赖1.2、添加Redis环境配置1.3、编写服务验证逻辑&#xff0c;通过 aop 代理方式实现1.4、在相关的业务接口上&#xff0c;增加SubmitLimit注解即可2、小结前言 在上一篇文章中&#xff0c;我们详细的介绍了随着下单流量逐渐上升&#…

【PyTorch】第二节:梯度的求解

作者&#x1f575;️‍♂️&#xff1a;让机器理解语言か 专栏&#x1f387;&#xff1a;PyTorch 描述&#x1f3a8;&#xff1a;PyTorch 是一个基于 Torch 的 Python 开源机器学习库。 寄语&#x1f493;&#xff1a;&#x1f43e;没有白走的路&#xff0c;每一步都算数&#…

python提取多个pdf特定页,并合并为新pdf文件

文章目录1&#xff0c;代码结构2&#xff0c;代码详解2.1&#xff0c;将范围字符串转成list2.2&#xff0c;获取pdf文件特定页2.3&#xff0c;将pdf页list合并为pdf文件并保存2.4&#xff0c;遍历所有要合并的文件&#xff0c;进行合并2.5&#xff0c;给出要合并的pdf文件及范围…

大模型学习

大模型学习计算机视觉方向ViTImage Token EmbeddingMulti-head Self-attentionStable Diffusionstable diffusion支持功能stable diffusion整体结构ClipText如何训练图像信息创建器&#xff08;Image information creator&#xff09;自动编码解码器&#xff08;降噪绘制图形&a…

One Note插件——gem for onenote的安装

文章目录一、前言二、报错原因三、解决方法一、前言 平时写笔记都是用的OneNote来记录&#xff0c;但是Onenote没有 Markdown编辑器 ,写起来很不方便&#xff0c;搜索了解后知道gem for OneNote这个插件&#xff0c;于是下载安装了&#xff0c;但是插件每次都要手动勾选&#…