【C语言学习笔记 --- 动态内存管理】
C语言程序设计笔记---029
- C语言之动态内存管理
- 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、柔性数组介绍
- 4.1、柔性数组的使用
- 5、结语
C语言之动态内存管理
前言:
通过C语言自定义类型的知识,这篇将对动态内存管理,进行深入学习和巩固相关的知识。
/知识点汇总/
1、介绍动态内存管理
概念:动态内存管理是指在程序运行时,根据需要动态地分配和释放内存空间的过程。
那么什么时候需要应用到动态分配内存呢?
通常来说,我们经常使用的数组,一般都是定长数组,因为在内存中申请的空间是固定的;
比如,当我明确知道需要采集100个人的成绩的时候,那么就可以使用指明长度的数组 int arr[101];
那么当我不知道采集多少人的时候,长度定1000,可能采集全校学生的信息就太小,又可能采集一个班级又太大,导致分配的空间资源得不到很好的利用,甚至浪费。
所以此时,根据C语言标准,它赋予程序员一种权力:能够动态申请和管理内存空间
目的是,合理的利用存储空间使得效率更高,让我们有能力灵活的运用而得心应手。
值得注意的是:动态内存管理的操作是在 堆区 上进行的操作的
2、动态内存函数的介绍
在C/C++中,动态内存管理主要通过malloc、calloc、realloc和free等函数来实现。
其中,malloc函数用于分配指定大小的内存空间,calloc函数用于分配指定数量和大小的内存空间并初始化为0,realloc函数用于重新分配已经分配的内存空间,而free函数则用于释放已经分配的内存空间。
2.1、malloc和free函数
malloc函数:
原型:void* malloc(size_t size);
头文件:<stdlib.h>
功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针
返回值:
如果开辟成功,则返回一个指向开辟好的空间的指针
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要判定检查(否则可能为野指针)
free函数
原型:void free( void memblock );
头文件:<stdlib.h>
功能:释放申请的内存空间,通常与申请内存空间的函数malloc()结合使用,可以释放由 malloc()、calloc()、realloc() 等函数申请的内存空间。
补充:
1)、返回值的类型是void,所以malloc函数并不确定开辟空间的类型,由使用者自己决定。
2)、如果参数size为0,malloc的行为是标准是未定义的,取决于当前的编译器。
3)、当然我们申请空间也存在失败的情况,所以需要考虑各种情况,做到代码的严谨性。
示例如下所示:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>//调用INT_MAX需申明的头文件
int main()
{//申请一块空间,用来存放10个整型int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");//开辟空间失败,打印错误原因return 1;}//当申请的空间太大或被占用就会出现失败的情况int* p2 = (int*)malloc(INT_MAX*4);//INT_MAX开辟一个整型最大值的空间if (p2 == NULL){perror("malloc");//开辟空间失败,打印错误原因return 1;}return 0;
}
关于动态内存的释放
分为两种释放方式:
1.主动释放:通过free函数主动释放空间
2.被动释放:程序退出后,被操作系统回收释放
正常情况下:谁申请谁释放,万一自己不释放,也得交代给别人释放
补充:
1)、如果参数ptr指向的空间不是动态开辟的,那么free函数的行为就是C标准未定义的
2)、如果参数ptr是NULL指针,则函数什么事都不做,即无用功
#include <stdio.h>
#include <stdlib.h>int main()
{//申请一块空间,用来存放10个整型int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");//开辟空间失败,打印错误原因return 1;}//动态内存的使用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}//释放动态内存空间 -- freefree(p);p = NULL;//必须将p指针开辟的空间置为空,避免导致野指针return 0;
}
2.2、calloc函数
原型:void* calloc(size_t num,size_t size);
功能:
为Num个大小为size的元素开辟一块空间,并且把空间的每一个字节初始化为0;
与malloc函数的区别只在于callloc会返回地址之前把申请的空间的每一个字节初始化为全0
头文件:<stdlib.h>
与malloc函数对比:
1)、功能对比:calloc会把每一个字节初始化为全0,而malloc不会初始化
2)、函数原型对比:
void* calloc(size_t num,size_t size);
void* malloc(size_t size);
示例如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)malloc(10 * sizeof(int));if (p == NULL){perror("malloc");return 1;}//打印int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));//随机值,堆区 --- 不是ccccc}//释放free(p);p = NULL;int* p2 = (int*)calloc(10 , sizeof(int));if (p2 == NULL){perror("calloc");return 1;}//打印int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p2 + i));//0,堆区 --- calloc初始化}//释放free(p2);p2 = NULL;return 0;
}
2.3、realloc函数
原型:void* realloc(void* ptr,size_t size);
功能:使得动态内存管理更灵活,即对动态内存空间进行调整
头文件:<stdlib.h>
返回值:
返回的是一个void类型的指针:调用成功。(这就要求在你需要的时候进行强制类型转换)
返回NULL:当需要扩展的大小(第二个参数)为0并且第一个参数不为NULL时。此时原内存变成“free(游离)”的了。
返回NULL:当没有足够的空间可供扩展的时候。此时,原内存空间的大小维持不变。
realloc函数开辟空间的多种情况
1.特殊情况:后续空间可能被占用,不能直接增加
那么realloc函数会找一块新的空间,足够一次性能全方进所有数据的空格,并会将旧空间的数据,一同拷贝到新空间
其次,完成新空间开辟拷贝后,就释放旧空间,最后返回指向新空间的地址/指针
2.正常情况:原有空间之后有足够大的空间,返回调整之后的内存起始地址
示例如下所示:
#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;//使用空间存储数据for (i = 0; i < 10; i++){p[i] = i;}//打印for (i = 0; i < 10; i++){printf("%d ", *(p + i));//0,堆区 --- calloc初始化}//空间不够时,调整空间为20个整型的空间int* ptr = (int*)realloc(p, 20 * sizeof(int));if (ptr != NULL)//开辟空间成功{p = ptr;}//释放free(p);return 0;
}
realloc函数开辟空间的特殊情况:后续空间可能被占用,不能直接增加
#include <stdio.h>
#include <malloc.h>int main(int argc, char* argv[]){char *p,*q;p = (char *)malloc(10);q = p;p = (char *)realloc(p,10);printf("p=0x%x/n",p);printf("q=0x%x/n",q);return 0;}
输出结果:realloc后,内存地址发生了变化 p=0x431a70 q=0x431a70
#include <stdio.h>
#include <malloc.h>
int main(int argc, char* argv[])
{char *p,*q;p = (char *)malloc(10);q = p;p = (char *)realloc(p,1000);printf("p=0x%x/n",p);printf("q=0x%x/n",q);return 0;
}
输出结果:realloc后,内存地址发生了变化 p=0x351c0 q=0x431a70
realloc函数平替mallloc函数的技巧使用:
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)realloc(NULL, 40);//等价 -- malloc(40);if (p == NULL){perror("realloc");return 1;}free(p);p = NULL;return 0;
}
3、动态内存管理过程中,一些常见的错误
在动态内存管理过程中,需要注意避免一些常见的错误:
如对NULL指针的解引用操作、对动态开辟的内存进行越界访问、对非动态开辟的内存free、使用free释放动态内存的一部分、对同一块动态内存多次释放等。
3.1、对NULL指针的解引用操作
#include <stdio.h>
int main()
{int* p = (int*)malloc(40);//当此时不作返回值判断时,则有可能是对空指针进行操作,非法访问//野指针*p = 20;//解决办法:添加判断后执行//if (p == NULL)//{// //...//}return 0;
}
3.2、对动态内存开辟的空间的越界访问
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;//越界访问,对开辟的空间越界访问了for (i = 0; i <= 10; i++){p[i] = i;}for (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
3.3、对非动态开辟内存使用了free函数
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;//对非动态内存空间释放空间 -- errorfree(p);p = NULL;return 0;
}
3.4、使用free释放一块动态开辟内存的一部分
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (p == NULL){perror("calloc");return 1;}int i = 0;for (i = 0; i < 5; i++){*p = i;p++;}// 0 1 2 3 4 0 0 0 0 ---- errorfor (i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);//只释放了一部分动态开辟的内存空间//更好的办法是引用,另一个指针变量,使得始终保存p的起始地址p = NULL;return 0;
}
3.5、对同一块动态内存多次释放
#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//...free(p);//error//解决办法:始终与free搭配释放后置为空指针//p = NULL;//有了空指针,以免后面再次释放时,及时报错提醒//....free(p);p = NULL;return 0;
}
3.6、动态开辟内存,忘记释放(导致内存泄漏)
#include <stdio.h>
#include <stdlib.h>
void test()
{int* p = (int*)malloc(40);if (p == NULL){perror("malloc");return 1;}//不释放 -- error//....//解决办法1:谁申请谁及时释放//free(p);//p = NULL;
}
int main()
{test();//在函数内开辟的动态内存,不释放//解决办法2:使void* test 设置返回值--->int* test(),返回p//用指针变量接收p//int* ret = test();// free(ret);// ....while (1);return 0;
}
4、柔性数组介绍
概念:C99标准中,规定结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员;柔性数组是结构体的成员
比如:
typedef struct st_type
{int i;int a[0];//柔性数组成员
}st_type;
4.1、柔性数组的使用
方法一:使用数组的方法:
#include <stdio.h>
#include <stdlib.h>
struct S
{char c;//1int i;//4int arr[];//未知大小的数组 -- 柔性数组成员
};
int main()
{//printf("%zd\n", sizeof(struct S));//8,sizeof返回的这种结构大小不包括柔性数组的内存//即,不包括柔性数组的内存大小。不做计算struct S* ps = (struct s*)malloc(sizeof(struct S) + 20);if (ps == NULL){perror("malloc");return 1;}ps->c = 'w';ps->i = 100;int i = 0;for (i = 0; i < 5; i++){ps->arr[i] = i;}//打印for (i = 0; i < 5; i++){printf("%d ", ps->arr[i]);}//如果空间不够,就可使用柔性数组和内存函数搭配,灵活使用动态内存struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40);if (ptr != NULL){ps = ptr;}else{perror("realloc");return 1;}//释放free(ps);ps = NULL;return 0;
}
方法二:使用指针的方法
#include <stdio.h>
#include <stdlib.h>
struct S
{char c;//1int i;//4int* data;
};
int main()
{struct S* ps = (struct s*)malloc(sizeof(struct S) + 20);if (ps == NULL){perror("malloc1");return 1;}ps->c = 'w';ps->i = 100;ps->data = (int*)malloc(20);if (ps->data == NULL){perror("malloc2");return 1;}int i = 0;for (i = 0; i < 5; i++){ps->data[i] = i;}//打印for (i = 0; i < 5; i++){printf("%d ", ps->data[i]);}//如果空间不够,就可使用柔性数组和内存函数搭配,灵活使用动态内存int* ptr = (int*)realloc(ps->data, 40);if (ptr == NULL){perror("realloc");return;}//释放free(ps->data);ps->data = NULL;free(ps);ps = NULL;return 0;
}
小结:
1.结构中的柔性数组成员前面必须至少一个其他成员
2.sizeof返回的这种结构大小不包括柔性数组的内存
3.包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
对比两种方法:
柔性数组加动态内存与相比指针加动态内存的方法指针的方法更好
1.方便内存释放
2.有利于访问速度
5、结语
掌握动态内存管理用于利于我们把知识嚼碎的消化,从而更好地理解和控制内存,提高程序的效率和性能,减少内存碎片和错误,增强程序的灵活性和适应性。同时,也要避免错误和内存泄漏的可能性。
名人名言分享:
“我愿意投入时间和精力去学习,因为我知道这将使我在未来的生活中受益。” — 爱因斯坦
“生活就像海洋,只有意志坚强的人,才能到达彼岸。” — 马克思
“只有那些敢于相信自己内心有某种比生存更重要的东西的人,才能过上所想象的那种充满快乐的生活。” — 契诃夫
相关文章:
【C语言学习笔记 --- 动态内存管理】
C语言程序设计笔记---029 C语言之动态内存管理1、介绍动态内存管理2、动态内存函数的介绍2.1、malloc和free函数2.2、calloc函数2.3、realloc函数 3、动态内存管理过程中,一些常见的错误3.1、对NULL指针的解引用操作3.2、对动态内存开辟的空间的越界访问3.3、对非动…...
Nougat来了,能否成为pdf格式转换的新神器?
Nougat来了,能否成为pdf格式转换的新神器? 论文链接:https://arxiv.org/pdf/2308.13418v1.pdf 项目地址:https://github.com/facebookresearch/nougat What happened?🤨 科学知识主要存储在书籍和科学期…...
C++文件和流
到目前为止,我们已经使用了 iostream 标准库,它提供了 cin 和 cout 方法分别用于从标准输入读取流和向标准输出写入流。 本教程介绍如何从文件读取流和向文件写入流。这就需要用到 C 中另一个标准库 fstream,它定义了三个新的数据类型&#x…...
代码随想录算法训练营第23期day31|贪心算法理论基础、455.分发饼干、376. 摆动序列、53. 最大子序和
目录 一、贪心算法理论基础 二、(leetcode 455)分发饼干 三、(leetcode 376)摆动序列 四、(leetcode 53)最大子序和 一、贪心算法理论基础 1.什么是贪心 贪心的本质是选择每一阶段的局部最优…...
mdadm命令详解及实验过程
mdadm命令详解及实验过程 ⼀.概念 mdadm是multiple devices admin的简称,它是Linux下的⼀款标准的软件 RAID 管理⼯具,作者是Neil Brown ⼆.特点 mdadm能够诊断、监控和收集详细的阵列信息 mdadm是⼀个单独集成化的程序⽽不是⼀些分散程序的集合&#…...
推荐几个程序员必逛的个人技术博客网站
1、美团技术团队 地 址: 美团技术团队简 介:美团技术团队的博客,干货满满。推荐指数:⭐⭐⭐⭐⭐ 2、阮一峰的网络日志 地 址: 阮一峰的个人网站 - Ruan YiFengs Personal Website简 介:大神阮一峰,博客风格真正…...
Python桌面应用之XX学院水卡报表查询系统(Tkinter+cx_Oracle)
一、功能样式 Python桌面应用之XX学院水卡报表查询系统功能: 连接Oracle数据库,查询XX学院水卡操作总明细报表,汇总数据报表,个人明细报表,进行预览并且支持导出报表 1.总明细报表样式 2.汇总明细样式 3.个人明细…...
ubuntu 中使用Qt连接MMSQl,报错libqsqlodbc.so: undefined symbol: SQLAllocHandle
Qt4.8.7的源码编译出来的libqsqlodbc.so,在使用时报错libqsqlodbc.so: undefined symbol: SQLAllocHandle,需要在编译libqsqlodbc.so 的项目pro文件加上LIBS -L/usr/local/lib -lodbc。 这里的路径根据自己的实际情况填写。 编辑: 使用uni…...
笔试,猴子吃香蕉,多线程写法
package demo;import java.util.concurrent.CountDownLatch;/*** description: 猴子吃香蕉* author: wxm* create: 2023-10-23 14:01**/ public class Main {public static void main(String[] args) throws InterruptedException {Monkey[] m new Monkey[3];Resource r new …...
安装docker ,更换docker版本
docker dockerd & containerd Dockerd(Docker 守护进程)在其底层使用 Containerd 来管理容器。Containerd 是一个开源的容器运行时管理器,由 Docker 公司于2017年开发并开源,它负责实际的容器生命周期管理。 以下是 Docker 守…...
英语小作文写作模板及步骤(1)
...
编写hello驱动程序
hello的驱动编写 编写驱动程序的步骤 1.确定主设备号,也可以让内核分配 2.定义自己的 file_operations 结构体 3.实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构 体 4.把 file_operations 结构体告诉内核:regist…...
ZYNQ中断例程
GPIO 中断系统初始化流程: 第一步:初始化 cpu 的异常处理功能 第二步:初始化中断控制器 第三步:向 CPU 注册异常处理回调函数; 第四步:将中断控制器中的对应中断 ID 的中断与中断控制器相连接 第五步:设置 …...
常用linux命令 linux_cmd_sheet
查看文件大小 ls -al 显示每个文件的kb大小 查看系统日志 dmesg -T | tail 在 top 命令中,RES 和 VIRT(或者 total-vm)是用来表示进程内存使用的两个不同指标,它们之间有以下区别: RES(Resident Set Size…...
【proteus】8086 写一个汇编程序并调试
参考书籍:微机原理与接口技术——基于8086和Proteus仿真(第3版)p103-105,p119-122. 参考程序是p70,例4-1 在上一篇的基础上: 创建项目和汇编文件 写一个汇编程序并编译 双击8086的元件图: …...
大数据之LibrA数据库常见术语(四)
Failover 指当某个节点出现故障时,自动切换到备节点上的过程。反之,从备节点上切换回来的过程称为Failback。 Freeze 在事务ID耗尽时由AutoVacuum Worker进程自动执行的操作。FusionInsight LibrA会把事务ID记在行头,在一个事务取得一行时&…...
Docker基础知识
文章目录 Docker Docker 一次构建,处处运行,类似于JVM 虚拟机是软件硬件(需要Hypervisors实现硬件资源虚拟化): 资源占用大启动慢(虚拟机是分钟级,Docker是秒级)冗余步骤多 sha2…...
swoole 是什么?
Swoole是一个为PHP用C和C编写的基于事件的高性能异步& 协程并行网络通信引擎; 使 PHP 开发人员可以编写高性能的协程 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IO…...
我想要一个勋章
目录 一、背景二、过程三、总结 一、背景 十年前结缘,也许是冥冥中自有天注定,注定要给自己多加一个今天的节日。 二、过程 一个勋章,一个有意义的标志。 一个勋章,一个时间轮上的帧。 一个勋章,一个二进制的节点。…...
微信小程序设计之主体文件app-json-pages
一、新建一个项目 首先,下载微信小程序开发工具,具体下载方式可以参考文章《微信小程序开发者工具下载》。 然后,注册小程序账号,具体注册方法,可以参考文章《微信小程序个人账号申请和配置详细教程》。 在得到了测…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
