【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)
文章目录
- 引言
- 一、为什么要动态内存分配
- 二、动态内存分配的相关函数
- 2.1 malloc
- 2.2 free
- 2.3 calloc
- 2.4 realloc
- 三、常见的动态内存的错误
- 3.1 对NULL指针的解引用
- 3.2 对动态内存越界访问
- 3.3 对非动态内存释放
- 3.4 对动态内存部分释放
- 3.5 对动态内存多次释放
- 3.6 未对动态内存释放(内存泄漏)
- 四、动态内存经典笔试题分析
- 4.1 题目一
- 4.2 题目二
- 4.3 题目三
- 4.4 题目四
- 五、柔性数组
- 5.1 柔性数组的特点
- 5.2 柔性数组的使用
- 六、C/C++中程序内存区域划分
引言
学习专栏:
《零基础学C语言》
《数据结构世界》
俗话说的好,要想学好数据结构(数据结构世界,对数据结构感兴趣的小伙伴可以移步),就必须学好以下三方面知识:
- 指针
不允许你还不了解指针的那些事(一)(内存和地址+指针变量+指针运算+野指针+传址调用)
不允许你还不了解指针的那些事(二)(数组传参的本质+冒泡排序+数组指针+指针数组) - 结构体
自定义类型:结构体(你真的掌握了内存对齐,位段吗?) - 动态内存管理
前两方面的知识在往期已经详细讲解,今天我们就来学习最后一方面的知识——动态内存管理
一、为什么要动态内存分配
我们已经掌握的内存开辟方式有:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在申明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整
所以,为了能在程序运行中,根据需求灵活地调整空间大小,C语言引入了动态内存管理。
二、动态内存分配的相关函数
2.1 malloc
C语言提供了一个动态内存开辟的函数:
void* malloc (size_t size);
这个函数可以开辟一块连续的内存空间,并返回指向这块空间的指针
- 如果开辟成功,则返回指向开辟好空间的指针。
- 如果开辟失败,则返回NULL 指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
int main()
{//动态开辟10个整型空间int* a = (int*)malloc(10 * sizeof(int));if (a == NULL)//开辟不成功,返回NULL{perror("malloc fail");return 1;}//开辟成功,使用该空间//...return 0;
}
注意:malloc(0)——开辟0个字节空间,是标准未定义行为,具体取决于编译器
2.2 free
C语言提供了另外一个函数free,可以动态内存的释放和回收:
void free (void* ptr);
free函数用来释放动态开辟的内存。
- 如果参数 ptr 是指向动态内存空间的指针,则释放该空间
- 如果参数 ptr 是NULL指针,则不作任何处理。
int main()
{//动态开辟10个整型空间int* a = (int*)malloc(10 * sizeof(int));if (a == NULL)//开辟不成功,返回NULL{perror("malloc fail");return 1;}//开辟成功,使用该空间//使用完毕,释放该空间free(a);a = NULL;return 0;
}
注意:如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
2.3 calloc
C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。
void* calloc (size_t num, size_t size);
其实,它与malloc很相似,区别在于calloc会将开辟的连续空间,每个字节都初始化为0
int main()
{//动态开辟10个整型空间并初始化为0int* a = (int*)calloc(10, sizeof(int));if (a == NULL)//开辟不成功,返回NULL{perror("calloc fail");return 1;}//开辟成功,使用该空间//使用完毕,释放该空间free(a);a = NULL;return 0;
}
2.4 realloc
realloc函数的出现让动态内存管理更加灵活。
为了合理的运用内存,所以在开辟空间后觉得太大或太小,就可以使用realloc进行调整。
void* realloc (void* ptr, size_t size);
realloc调整成功,会返回调整后指向这块空间的指针
- ptr 是调整前空间的指针
- size 是调整后的新大小
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
- 情况1:原有空间之后有足够大的空间
这个时候,realloc会直接在原有空间后面直接追加开辟。

- 情况2:原有空间之后没有足够大的空间
这个时候,realloc就会再找一块足够大空间,一次性开辟完,再将原数据拷贝过去。

int main()
{//动态开辟10个整型空间int* a = (int*)malloc(10 * sizeof(int));if (a == NULL)//开辟不成功,返回NULL{perror("malloc fail");return 1;}//调整空间,扩大为20个整型空间int* tmp = (int*)realloc(a, 20 * sizeof(int));if (tmp == NULL)//开辟不成功,返回NULL{perror("realloc fail");return 1;}//调整成功,将空间地址给aa = tmp;//使用空间...free(a);a = NULL;return 0;
}
注意:
- 用临时变量tmp来接受realloc调整后的空间地址
- 因为如果用a接收,万一调整失败返回NULL,还会丢掉原本空间的地址
三、常见的动态内存的错误
3.1 对NULL指针的解引用
void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}
3.2 对动态内存越界访问
void test()
{int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(-1);//终止程序}for(i=0; i<=10; i++){*(p+i) = i;//当i是10的时候越界访问}free(p);
}
3.3 对非动态内存释放
void test()
{int a = 10;int *p = &a;free(p);//ok?
}
3.4 对动态内存部分释放
void test()
{int *p = (int *)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}
3.5 对动态内存多次释放
void test()
{int *p = (int *)malloc(100);free(p);free(p);//重复释放
}
3.6 未对动态内存释放(内存泄漏)
因为程序终止会回收所有空间,所以这里用死循环来模拟程序运行中不释放动态内存,会导致内存泄漏。
int main()
{int* a = (int*)malloc(10 * sizeof(int));while(1);return 0;
}
总结:动态开辟的内存一定要释放,并且要正确释放
四、动态内存经典笔试题分析
请先自行思考哦,不要立马看解析~
请问以下题目运行Test 函数会有什么样的结果?
4.1 题目一
void GetMemory(char *p)
{p = (char *)malloc(100);
}
void Test(void)
{char *str = NULL;GetMemory(str);strcpy(str, "hello world");printf(str);
}
结果:
- 程序崩溃,内存泄漏
解析:
- GetMemory函数中,动态开辟了100个字节空间。但是p是一个形参,形参是实参的一份临时拷贝,对形参的影响无法改变实参,所以str还是NULL。
- 在运行strcpy函数时,因为要拷贝的目的地是NULL,所以程序崩溃。
- 同时,动态开辟的空间未释放,还导致了内存泄漏。
4.2 题目二
char *GetMemory(void)
{char p[] = "hello world";return p;
}
void Test(void)
{char *str = NULL;str = GetMemory();printf(str);
}
结果:
- 有可能打印出乱码
解析:
- GetMemory函数中,开辟了p数组。p是局部变量,出了函数作用域就会销毁(空间被回收)。
- 所以,str接收p的地址时,此时已经指向一块被回收的空间。
- 打印该空间的内容,是否为乱码,取决于该空间的内容是否被其余数据覆盖
4.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);
}
结果:
- 内存泄漏,有可能程序崩溃
解析:
- 这次进行传址调用,二级指针解引用,确实能改变外部str
- 但是接收到空间地址后,未进行检查,如果开辟失败,为NULL,则strcpy时依旧会程序崩溃
- 同时未对动态内存释放,造成内存泄漏
4.4 题目四
void Test(void)
{char *str = (char *) malloc(100);strcpy(str, "hello");free(str);if(str != NULL){strcpy(str, "world");printf(str);}
}
结果:
- 后果未知
解析:
- 假设能正常开辟动态内存
- strcpy拷贝完hello,并free进行释放
- 因为free不会自动将str置为NULL,所以以下判断不起作用,“拦不住”str这个野指针
- 最后strcpy将world拷贝到已被释放的空间,再进行打印(对已被释放的动态内存进行操作,后果未知,十分危险,很严重!)
五、柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后⼀个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
有些编译器会报错无法编译可以改成:
typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;
5.1 柔性数组的特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
int main()
{printf("%d\n", sizeof(type_a));//输出的是4return 0;
}
5.2 柔性数组的使用
int main()
{int i = 0;type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));//使用空间p->i = 100;for(i=0; i<100; i++){p->a[i] = i;}free(p);return 0;
}
这样柔性数组成员a,相当于获得了100个整型元素的连续空间。
六、C/C++中程序内存区域划分

C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!
💛 💙 💜 ❤️ 💚💓 💗 💕 💞 💘 💖
拜托拜托这个真的很重要!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信呢秒回哦。
相关文章:
【C语言】动态内存管理(C语言的难点与精华,数据结构的前置知识,你真的掌握了吗?)
文章目录 引言一、为什么要动态内存分配二、动态内存分配的相关函数2.1 malloc2.2 free2.3 calloc2.4 realloc 三、常见的动态内存的错误3.1 对NULL指针的解引用3.2 对动态内存越界访问3.3 对非动态内存释放3.4 对动态内存部分释放3.5 对动态内存多次释放3.6 未对动态内存释放&…...
最长子序列问题(LCS)--动态规划解法
题目描述: 如果Z既是X的子序列,又是Y的子序列,则称Z为X和Y的公共子序列。 如果给定X、Y,求出Y及其长度。 示例: 输入 ABCPDSFJGODIHJOFDIUSHGD OSDIHGKODGHBLKSJBHKAGHI 输出 SDIHODSHG 9 分析: c…...
实时流式计算 kafkaStream
文章目录 实时流式计算Kafka StreamKafka Streams 的关键概念KStreamKafka Stream入门案例编写SpringBoot 集成 Kafka Stream 实时流式计算 一般流式计算会与批量计算相比较 流式计算就相当于上图的右侧扶梯,是可以源源不断的产生数据,源源不断的接收数…...
西南科技大学模拟电子技术实验七(集成运算放大器的非线性应用)预习报告
一、计算/设计过程 说明:本实验是验证性实验,计算预测验证结果。是设计性实验一定要从系统指标计算出元件参数过程,越详细越好。用公式输入法完成相关公式内容,不得贴手写图片。(注意:从抽象公式直接得出结果,不得分,页数可根据内容调整) 预习计算内容根据运放的非线…...
Ubuntu与Windows通讯传输文件(FTP服务器版)(没用的方法,无法施行)
本文介绍再Windows主机上建立FTP服务器,并且在Ubuntu虚拟机上面访问Windows上FTP服务器的方法 只要按照上图配置就可以了 第二部:打开IIS管理控制台 右击网站,新建FTP站点。需要注意的一点是在填写IP地址的时候,只需要填写Window…...
2024年AI视频识别技术的6大发展趋势预测
随着人工智能技术的快速发展,AI视频识别技术也将会得到进一步的发展和应用。2023年已经进入尾声,2024年即将来临,那么AI视频识别技术又将迎来怎样的发展趋势?本文将对2023年的AI视频技术做一个简单的盘点并对2024年的发展趋势进行…...
一篇文章了解JDK的前世今生
我们每天都在开发Java,每天都在使用JDK,那么我们了解JDK的发展史吗,这篇文章将带你深入了解JDK的发展史。 JDK(Java Development Kit)是Java开发者工具包,是用于编写Java程序和运行Java程序的软件开发工具集。自从1995年Java语言首次发布以来,JDK已经经历了数十年的发展…...
Redisson出现问题总结
org.redisson.client.RedisAuthRequiredException: NOAUTH Authentication required… channel: 出现此问题的原因为没有redis权限。解决方案在setAddress()后面加上setPassword()方法。 config.useSingleServer().setAddress("redis://localhost:6379").setPasswo…...
领域驱动架构(DDD)建模
一、背景 常见的软件开发方式是拿到产品需求后,直接考虑数据库中表应该如何设计,这种方式已经将设计与业务需求脱节,而更多的是直接考虑应该如何实现了,这有点本末倒置。而DDD是从领域(问题域)为出发点进行的设计方法。 领域驱动…...
PostgreSQL从小白到高手教程 - 第38讲:数据库备份
PostgreSQL从小白到专家,是从入门逐渐能力提升的一个系列教程,内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容,希望对热爱PG、学习PG的同学们有帮助,欢迎持续关注CUUG PG技术大讲堂。 第38讲&#…...
OpenGL ES eglCreatePbufferSurface() 和 eglCreateWindowSurface() 的对比和使用
一、介绍 相同点: eglCreatePbufferSurface 和 eglCreateWindowSurface 都是 OpenGL ES 中用于创建不同类型的EGL表面的函数,以便在OpenGL ES中进行渲染。 不同点: 选择使用哪种表面类型取决于你的需求。如果你只是需要在内存中进行离屏渲…...
python之马尔科夫链(Markov Chain)
马尔可夫链(Markov Chain)是一种随机过程,具有“马尔可夫性质”,即在给定当前状态的条件下,未来状态的概率分布仅依赖于当前状态,而与过去状态无关。马尔可夫链在很多领域都有广泛的应用,包括蒙…...
数据库管理-第123期 Oracle相关两个参数(202301205)
数据库管理-第123期 Oracle相关两个参数(202301205) 最近在群聊中看到俩和Oracle数据库相关的俩参数,一个是Oracle数据库本身的,一个是来自于Weblogic的,挺有趣的,本期研究一下。(本期涉及参数…...
掌握vue中国际化使用及配置
文章目录 🍁i18n组件安装🍁项目中配置 vue-i18n🍁编写语言包🍁国际化的使用 随着互联网的普及和全球化的发展,开发国际化的应用程序已经成为一种趋势。因此,将 VUE 应用程序国际化是非常有必要的。 以下是…...
Ubuntu编译文件安装SNMP服务
net-snmp源码下载 http://www.net-snmp.org/download.html 编译步骤 指定参数编译 ./configure --prefix/root/snmpd --with-default-snmp-version"2" --with-logfile"/var/log/snmpd.log" --with-persistent-directory"/var/net-snmp" --wi…...
3D Web可视化平台助力Aras开发PLM系统:提供数据访问、可视化和发布功能
HOOPS中文网慧都科技是HOOPS全套产品中国地区指定授权经销商,提供3D软件开发工具HOOPS售卖、试用、中文试用指导服务、中文技术支持。http://techsoft3d.evget.com/ Aras是一个面向数字化工业应用的开放性平台,帮助世界领先的复杂互联产品制造商转变其产…...
Graphpad Prism10.1.0 安装教程 (含Win/Mac版)
GraphPad Prism GraphPad Prism是一款非常专业强大的科研医学生物数据处理绘图软件,它可以将科学图形、综合曲线拟合(非线性回归)、可理解的统计数据、数据组织结合在一起,除了最基本的数据统计分析外,还能自动生成统…...
【动态规划】路径问题_不同路径_C++
题目链接:leetcode不同路径 目录 题目解析: 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析: 题目让我们求总共有多少条不同的路径可到达右下角; 由题可得: 机器人位于…...
Python并发-线程和进程
一、线程和进程对应的问题 **1.进程:**CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 100%,CPU要读/写I/O(硬盘/内存),I/O在很短的时间就可…...
微信小程序适配方案:rpx(responsive pixel响应式像素单位)
小程序适配单位:rpx 规定任何屏幕下宽度为750rpx 小程序会根据屏幕的宽度自动计算rpx值的大小 Iphone6下:1rpx 1物理像素 0.5css 小程序编译后,rpx会做一次px换算,换算是以375个物理像素为基准,也就是在一个宽度…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
