【c语言】动态内存管理(超详细)
他治愈了身边所有人,唯独没有治愈他自己—超脱
csdn上的朋友你们好呀!!今天给大家分享的是动态内存管理
👀为什么存在动态内存分配
我们定义的局部变量在栈区创建
int n = 4;//在栈上开辟4个字节大小int arr[10] = { 0 };//在栈上开辟连续的40个字节大小
上述变量创建的特点
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了
int main()
{int n;scanf("%d",&n);int arr[n];}
上述代码只能在C99标准编译器上才行,vs系列编译器均不支持,那我们怎么才能在运行的时候,实现上述变长数组的代码呢??这时候就只能试试动态存开辟了。
👀 动态内存函数的介绍
malloc
函数功能:开辟内存块
参数size_t:需要申请的字节数
返回值:申请失败返回空指针,申请成功返回指向申请该空间首地址的指针
头文件:stdlib.h
注意:返回指针的类型是void*,这时候需要你把该指针强制类型转化为你想要的类型,这样方便访问,以及解引用,malloc申请来的空间是连续的,但是多次malloc来的是不连续的
malloc的使用
int main()
{int*p=(int*) malloc(40);//申请了40个字节,强制转化为int*类型指针if (p == NULL)//如果返回空指针的话,申请失败{perror("malloc:");//打印错误信息return 1;//非正常退出}for (int i = 0; i < 10; i++){*(p + i) = i;//对每一个四个字节大小的元素赋值,这里*(p+i)的本质就是p[i];printf("%d", *(p + i));//打印每个元素}return 0;//程序正常退出}
free
功能:释放内存块
参数:指针接收要释放内存块的首地址
头文件:stdlib.h
返回值:无
了解了这些之后,我们试一下释放刚才malloc来的内存块
int main()
{int i = 0;int*p=(int*) malloc(40);if (p == NULL){perror("malloc:");return 1;}for (int i = 0; i < 10; i++){*(p + i) = i;printf("%d", *(p + i));}free(p);//指针接收要释放内存块的首地址p = NULL;//很有必要否则p为野指针return 0;}
当p所指向的申请的空间释放时,p指针指向随机位置,p变成野指针。
如果我们不释放动态内存申请的内存的时候,程序结束,动态申请内存由操作系统自动回收,如果不用free函数释放申请好的空间,就会在程序运行结束前一直存在于堆中,造成内存泄漏
int main()
{while (1){malloc(1000);}return 0;}
我是不知天高地厚的年轻人哈哈哈哈哈
calloc
功能:申请一个数组在内存中,并且初始化为0;
参数:size_t num申请数组元素的个数,size_t size每个元素的字节大小
返回值:申请失败返回空指针,申请成功返回指向申请该空间首地址的指针
头文件:stdlib.h
calloc函数使用
int main()
{int i = 0;int*p=(int*) calloc(10,sizeof(int));//申请10个元素,每个元素字节大小4if (p == NULL)//如果返回空指针的话,申请失败{perror("calloc:");//打印错误信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印初始化的值}free(p);p = NULL;return 0;}
malloc和calloc的区别:与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
我们可以看看malloc有没有先初始化
int main()
{int* p = (int*)malloc(40);//申请了40个字节,强制转化为int*类型指针if (p == NULL)//如果返回空指针的话,申请失败{perror("malloc:");//打印错误信息return 1;//非正常退出}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));//打印每个元素}free(p);//指针接收要释放内存块的首地址p = NULL;//很有必要否则p为野指针return 0;//程序正常退出}
可以看到是未初始化的,放的随机值
realloc
功能:内存块的扩容
参数:第一个参数接收要扩容内存块的首地址,扩容后总字节大小(包括原来的字节大小)
头文件:stdlib.h
返回值:
realloc函数使用
int main()
{int* p = (int*)malloc(40);//申请了40个字节,强制转化为int*类型指针if (p == NULL)//如果返回空指针的话,申请失败{perror("malloc:");//打印错误信息return 1;//非正常退出}for (int i = 0; i < 10; i++)//循环打印扩容前的元素{*(p + i) = i;printf("%d ", *(p + i));}int* ptr = (int*)realloc(p, 80);//原空间够用ptr==p,不够用的话ptr存放新地址if (ptr != NULL)//扩容成功{p = ptr;//原空间够用ptr==p,不够用的话ptr存放新地址,重新将新地址给p}for (int i = 10; i < 20; i++)//扩容后新空间的{*(p + i) = i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
编译运行
👀常见的动态内存错误
1.对NULL指针的解引用操作
int main()
{int* p = (int*)malloc(1000);int i = 0;//if (p ==NULL)//{// return 1;//}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
当malloc申请内存失败,p=NULL,i=0,相当于给空指针解引用
解决办法:对malloc函数返回值做出判断
int main()
{int* p = (int*)malloc(1000);int i = 0;if (p ==NULL){return 1;}for (i = 0; i < 250; i++){*(p + i) = i;}free(p);p = NULL;return 0;
}
2. 对动态开辟空间越界访问
int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <=25; i++)//越界访问{*(p + i) = i;}free(p);p = NULL;return 0;
}
编译运行
解决方法:人为检查是否越界
修改:
int main()
{int* p = (int*)malloc(100);int i = 0;if (p ==NULL){return 1;}for (i = 0; i <25; i++)//=25变成<25{*(p + i) = i;}free(p);p = NULL;return 0;
}
3.对非动态开辟内存进行free
int main()
{int a = 10;int* p = &a;free(p);p = NULL;return 0;
}
编译运行
解决方案:你别手贱(🙂)
4.使用free释放一块动态开辟内存的一部分
int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}free(p);p = NULL;return 0;}
编译运行
解决方案:别改变p指向的地址,或者用一个指针记录申请内存的首地址
plan1:
int main()
{int* p = (int*)malloc(100);if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*(p+i)= i;printf("%d ", *(p + i));}free(p);p = NULL;return 0;}
plan2:
int main()
{int* p = (int*)malloc(100);int* q = p;if (p == NULL){return 1;}int i = 0;for (i = 0; i < 10; i++){*p= i;printf("%d ", *p);p++;}free(q);q = NULL;return 0;}
5.多次free已经释放的内存
int main()
{int* p = malloc(40);if (p == NULL){return 1;}free(p);free(p);p = NULL;return 0;
}
编译运行
解决方案:别多次free已经释放的内存(滑稽)
6.动态开辟内存忘记释放
见上面
👀几个经典的笔试题
1
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
分析:定义一个char*str,让他指向空,str接收GetMemory()函数返回来的地址,进入GetMemory()函数,return p,只能把p[]的首地址传回去,而p[]是局部变量,出GetMemory(),p[]销毁,当你传回去的时候,str接收的是野地址,str为野指针。打印不出来hello world
编译运行:
2.
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
分析:
3.
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
分析:
但是没有free释放内存
运行编译
4.
void main(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
分析:
运行编译
👀 柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最
后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员
柔性数组的特点
结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
我们可以定义一个结构体
struct pp {int a;int b[];};
int main()
{printf("%d", sizeof(struct pp));}
编译运行
确实没有包括柔性数组大小
柔性数组的使用
struct pp {int a;int b[];//柔性数组成员
};
int main()
{struct pp* p = (struct pp*)malloc(sizeof(struct pp) + 10 * sizeof(int));//malloc中第一个元素大小+柔性数组字节大小p->a = 4;//赋值for (int i = 0; i < 10; i++){p->b[i] = i;//赋值}for (int i = 0; i < 10; i++){printf("%d ", p->b[i]);//打印柔性数组}printf("%d ", p->a);free(p);//free掉malloc来的空间p = NULL;//p置为空指针}
我们能不能用指针代替那个柔性数组呢,我们可以将指针指向的那个地方malloc来使用
定义一个结构体
struct pp {int a;int* p;
};
int main()
{struct pp* q = (struct pp*)malloc(sizeof(struct pp));//申请结构体大小的内存if (q == NULL)//判断申请是否成功{return 1;//异常退出}q->p = (int*)malloc(10*sizeof(int));if (q->p == NULL)//判断申请是否成功{return 1;//异常退出}q->a = 10;//赋值for (int i = 0; i < 10; i++){q->p[i]= i;赋值}printf("%d ", q->a);for (int i = 0; i < 10; i++){printf("%d ", q->p[i]);}free(q->p);//free掉p指针指向的另一块申请空间的内存q->p = NULL;//指针置空,防止野指针free(q);//free掉q指向的申请的内存q = NULL;}
分析:
malloc过程
释放过程
编译运行
上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处: 第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片,根据局部性原理,连续存放的数据,cup从缓冲区读取的快,缓存区从内存中读取的快。
总结
本片分享了四个动态内存函数,以及常见动态内存错误,几个经典的笔试题,以及柔性数组的概念,如果你觉得对你有帮助的话,希望能留下你的点赞,关注加收藏,如果有不对的地方,可以私信我,谢谢各位佬们!!!
相关文章:

【c语言】动态内存管理(超详细)
他治愈了身边所有人,唯独没有治愈他自己—超脱 csdn上的朋友你们好呀!!今天给大家分享的是动态内存管理 👀为什么存在动态内存分配 我们定义的局部变量在栈区创建 int n 4;//在栈上开辟4个字节大小int arr[10] { 0 };//在栈上开…...

Linux/centos上如何配置管理NFS服务器?
Linux/centos上如何配置管理NFS服务器? 1 NFS基础了解1.1 NFS概述1.2 NFS工作流程 2 安装和启动NFS服务2.1 安装NFS服务器2.2 启动NFS服务 3 配置NFS服务器和客户端3.1 配置NFS服务器3.2 配置NFS客户端 4 实际示例4.1 基本要求4.2 案例实现 1 NFS基础了解 NFS&…...
Element组件浅尝辄止5:Empty 空状态组件
Empty空状态组件:空状态时的占位提示。 如第一次进入当前功能模块时,数据状态为空,则展示空状态,可用到Empty组件 1.How? <el-empty description"描述文字"></el-empty> 2.自定义图片 通过设置 image 属…...

【华为Datacom 综合拓扑案例—分享篇】
拓扑图 题目要求 实验要求: 1、PC1\PC2\PC3\PC4采用DHCP自动获取IP地址,SW5作为服务器,SW3和SW4作为中继 创建地址池ip pool huawei1和ip pool huawei2,租期都为2天 2、SW3与SW4做链路聚合,采用LACP模式。SW3作为主…...

springcloud3 使用openfegin实现getpost请求调用
一 项目介绍 1.1 工程介绍 1.consumer9008 2.provider9009 二 get请求 2.1 消费端 1.controller 2.service 2.2 提供者 1.提供者 2.3 测试请求 地址: http://localhost:9008/consumer/payment/nacos/2223 三 post请求 3.1 消费者 3.2 提供者 3.3 测试请求…...

【JVM】类装载的执行过程
文章目录 类装载的执行过程1.加载2.验证3.准备4.解析5.初始化6.使用7.卸载 类装载的执行过程 类装载总共分为7个过程,分别是 加载,验证,准备、解析、初始化、使用、卸载 1.加载 将类的字节码文件加载到内存(元空间)中。这一步会…...

FreeRTOS(独立看门狗监测任务执行与低功耗Tickless模式)
资料来源于硬件家园:资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、独立看门狗介绍 二、看门狗监测多任务执行思路 1、监测目标 2、监测方案 3、应用注意事项 三、看门狗监测多任务编程 1、STM32cubeMX配置 2、代码编写 四、低功耗Tickless模…...

预训练GNN:GPT-GNN Generative Pre-Training of Graph Neural Networks
一.文章概述 本文提出了一种自监督属性图生成任务来预训练GNN,使得其能捕图的结构和语义属性。作者将图的生成分为两个部分:属性生成和边生成,即给定观测到的边,生成节点属性;给定观测到的边和生成的节点属性…...

Python实现透明隧道爬虫ip:不影响现有网络结构
作为一名专业爬虫程序员,我们常常需要使用隧道代理来保护个人隐私和访问互联网资源。本文将分享如何使用Python实现透明隧道代理,以便在保护隐私的同时不影响现有网络结构。通过实际操作示例和专业的解析,我们将带您深入了解透明隧道代理的工…...

并发编程系列-CompletableFuture
利用多线程来提升性能,实质上是将顺序执行的操作转化为并行执行。仔细观察后,你还会发现在顺序转并行的过程中,一定会牵扯到异步化。举个例子,现在下面这段示例代码是按顺序执行的,为了优化性能,我们需要将…...
锁粒度的粗细与时空损耗互换
1 空间换时间的cases 1.1 redis的用户分组限流和用户定制的限流器 Redis 用户分组限流和用户定制的限流器:使用 Redis 进行用户分组限流或用户定制的限流意味着你使用 Redis 数据库来维护用户的访问限制。可以通过计数器、滑动窗口或令牌桶等算法来实现限流。用户…...
[Android 11]使用Android Studio调试系统应用之Settings移植(七):演示用AS编译错误问题
文章目录 1. 篇头语2. 系列文章3. AS IDE的配置3.1 AS版本3.2 Gradle JDK 版本4. JDK的下载5. AS演示工程地址6.其他版本JDK导致的错误1. 篇头语 距离2021年开始,系列文章发表已经有近两年了,依旧有网友反馈一些gitee上演示源码编译的一些问题,这里就记录一下。 2. 系列文章…...
MyBatis面试题
MyBatis面试题: 1、MyBatis是什么? Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建statement等繁杂的过程,开发者开发时只需要关注如何编写SQL语句…...

Lorenz系统最大lyapunov exponent的求解
首先看下Lorenz混沌系统: 赋予初始值,例如: 当然,初始值可以根据需要设定。 看下他的吸引子,很美: 看下他的分叉图:...
c#实现策略模式
下面是一个使用C#实现策略模式的示例代码: using System;// 策略接口 public interface IStrategy {void Execute(); }// 具体策略类A public class ConcreteStrategyA : IStrategy {public void Execute(){Console.WriteLine("具体策略A的执行逻辑");} …...

家纺行业小程序商城搭建指南
家纺行业作为一个不可或缺的消费领域,近年来备受关注。随着互联网的发展,小程序商城成为家纺行业拓展市场的新利器。搭建一个家纺行业小程序商城并不是一件困难的事情,只需要按照以下几个步骤进行操作,就能轻松上手。 首先&#x…...

Python语法基础--条件选择
学习目标 使用比较运算符编写布尔表达式。使用random.randint(a,b)或者random.random()函数来生成随机数。编写布尔表达式(AdditionQuiz)。使用单向if语句实现选择控制。使用单向if语句编程。使用双向if-else语句实现选择控制。使用嵌套if和多向if-elif-else语句实现选择控制。…...

visual studio 2017 运行的程序关闭后不能再运行?(visual studio建立项目之后退出,如何再次完整打开项目?)
在你储存项目的文件夹里面应该是这样的 里面.vcxproj后缀名的就是原来创建的项目,直接打开这个头文件源文件就会一起出来了! 真的管用,亲测有效。...

亚马逊feedback和review有什么区别
在亚马逊上,"Feedback"(反馈)和"Review"(评论)是两个不同的概念,它们在购物体验中起着不同的作用。 Feedback(反馈): 亚马逊的"Feedback"…...

新疆大学841软件工程考研
1.软件生产的发展经历了三个阶段,分别是____、程序系统时代和软件工程时代时代。 2.可行性研究从以下三个方面研究每种解决方法的可行性:经济可行性、社会可行性和_____。 3.HIPO图的H图用于描述软件的层次关系&…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...