【C语言】动态内存管理全解析:malloc、calloc、realloc与free的正确使用

C语言学习

动态内存分配
友情链接:C语言专栏


文章目录

  • C语言学习
  • 前言:
  • 一、为什么要有动态内存分配
  • 二、malloc和free
    • 2.1 malloc
    • 2.2 free
  • 三、calloc和realloc
    • 3.1 calloc
    • 3.2 realloc
  • 总结
  • 附录
    • 上文链接
    • 下文链接
    • 专栏


前言:

在C语言编程中,内存管理是开发者必须掌握的核心技能之一。静态内存分配虽然简单易用,但在实际开发中往往无法满足灵活多变的内存需求。动态内存分配技术为程序提供了运行时按需分配内存的能力,极大地增强了程序的灵活性和资源利用率。本文将深入讲解C语言中动态内存分配的四大关键函数:malloc、calloc、realloc和free,通过原理分析、代码示例和常见问题解析,帮助读者全面掌握动态内存管理的精髓,避免内存泄漏和野指针等常见问题。


一、为什么要有动态内存分配

咱们先来看一下咱们已经掌握的内存开辟方式有:

	int val = 10;//在栈空间上开辟四个字节char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间方式有两个特点:

空间开辟大小是固定的。
数组在申明的时候,必须指定数组的长度,数组空间⼀旦确定了大小不能调整

但是我们在写代码的时候对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
C语言 引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。

二、malloc和free

2.1 malloc

C语言提供了⼀个动态内存开辟的函数:

void* malloc (size_t size);

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

  • 如果开辟成功,则返回⼀个指向开辟好空间的指针。
  • 如果开辟失败,则返回⼀个 NULL 指针,因此malloc的返回值⼀定要做检查。
  • 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

2.2 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的(动态申请的内存如果不再使用,必须显式地释放并归还给操作系统),函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

  • 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  • 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头文件中。

代码示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = NULL;//开辟ptr = (int*)malloc(1000000000000);//动态开辟20个字节的内存if(ptr == NULL)//判断ptr指针是否为空(是否开辟成功){perror("malloc:");}//使用else{int i = 0;for (i = 0; i < 5; i++){*(ptr + i) = 0;}}//释放free(ptr);//释放ptr所指向的动态内存,传入的指针必须是要释放的内存空间的起始地址。//注意:free只是将这一块内存还给操作系统了,而对于ptr指针未作任何改变//此时,ptr就是野指针ptr = NULL;//即将野指针置空return 0;
}

三、calloc和realloc

3.1 calloc

C语言还提供了一个函数叫calloccalloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);

参数解释:

为 num 个大小为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

与函数 malloc 的区别:

只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));//开辟10个空间大小为int的内存空间if (NULL != p)//判断p指针是否为空(是否开辟成功){int i = 0;for (i = 0; i < 10; i++){//咱们并未主动赋值//直接打印printf("%d ", *(p + i));}}free(p);p = NULL;return 0;
}

输出:
在这里插入图片描述
所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。

3.2 realloc

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

void* realloc (void* ptr, size_t size);

参数解释:

ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。

那咱们就会有疑问,为什么返回值是调整之后的内存起始位置,起始位置不是一直没变吗,其实不是这样的,
realloc在调整内存空间的是存在两种情况:

  • 情况1:原有空间之后有足够够大的空间
  • 情况2:原有空间之后没有足够大的空间

图示:
在这里插入图片描述
情况1:
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2:
当是情况2 的时候,原有空间之后没有大小够多的空间时,扩展的方法是:在堆空间上另找⼀个合适大小的连续空间来使用。这样函数返回的是⼀个新的内存地址。
情况2图示:
在这里插入图片描述
知道了上述的两种情况,realloc函数的使用我们就要注意⼀些要点了。
看代码:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = (int*)malloc(100);if (ptr != NULL){//……}else{perror("malloc");}//扩展容量为1000个字节//完善代码//……return 0;
}

那咱们可以这样写吗:

	ptr = (int*)realloc(ptr, 1000);

不行,坚决不允许。
解释:

直接将realloc的返回值放到ptr中的话,如果realloc函数申请失败则会返回NULL,此时会使得ptr为NULL。
就是会导致:

  1. 内存泄漏(原内存无法释放)
  2. 无法访问原有数据

正确怎么写呢?
我们先将realloc函数的返回值放在p中,当p不为NULL,再放ptr中:

	int* p = NULL;p = (int*)realloc(ptr, 1000);if (p != NULL){ptr = p;}

这样是没有问题的。
那怎么把上面代码总结一下,完整的如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* ptr = (int*)malloc(100);if (ptr != NULL){//使用……}else{perror("malloc");return 1; // 直接退出,避免后续操作}//扩展容量//先将realloc函数的返回值放在p中,不为NULL,在放ptr中int* p = NULL;p = (int*)realloc(ptr, 1000);if (p != NULL){ptr = p;}//使用……//释放free(ptr);ptr = NULL;// 防止野指针(良好习惯)return 0;
}

所以,咱们要知道:
malloc、calloc 和 realloc 在内存分配失败时都会返回 NULL。


总结

良好的内存管理习惯不仅能避免程序崩溃和内存泄漏,还能提高代码的健壮性和可维护性。记住:动态分配的内存就像借来的书,用完后一定要记得归还(释放)!
对于这一部分内容,不难,但是容易出错,后续我会关于易错点等等再出一篇文章,帮助大家理解,谢谢观看至此!!!

附录

上文链接

《联合体完全指南:内存共享、大小计算与实战应用》

下文链接

《动态内存分配避坑指南:六大易错点解析与经典笔试题实战》

专栏

C语言专栏

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

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

相关文章

基于Arduino智能家居环境监测系统—以光照强度检测修改

2 相关技术与理论 2.1 Arduino 技术 Arduino 是一款广受欢迎的开源电子原型平台&#xff0c;由硬件和软件组成&#xff0c;为开发者提供了便捷且低成本的解决方案&#xff0c;尤其适用于快速搭建交互式电子项目&#xff0c;在本智能家居环境监测系统中担当核心角色。​ 硬件方…

前端上传 pdf 文件 ,前端自己解析出来 生成界面 然后支持编辑

要在前端解析 PDF 文件并生成可编辑界面&#xff0c;我们可以使用 PDF.js 库来解析 PDF 内容&#xff0c;然后将其转换为可编辑的 HTML 元素。 主要特点和工作原理如下&#xff1a; PDF 解析&#xff1a; 使用 Mozilla 的 PDF.js 库解析 PDF 文件内容&#xff0c;提取文本信息。…

Linux“一切皆文件“设计哲学 与 Linux文件抽象层:struct file与file_operations的架构解析

在Linux系统中&#xff0c;“一切皆文件”&#xff08;Everything is a file&#xff09;是一个核心设计哲学&#xff0c;它抽象了系统资源的访问方式&#xff0c;使得几乎所有硬件设备、进程、网络连接等都可以通过统一的文件接口&#xff08;如open()、read()、write()、clos…

蓝桥杯零基础到获奖-第3章 C++ 变量和常量

蓝桥杯零基础到获奖-第3章 C 变量和常量 文章目录一、变量和常量1.变量的创建2.变量初始化3.变量的分类4.常量4.1 字⾯常量4.2 #define定义常量4.3 const 定义常量4.4 练习练习1&#xff1a;买票https://www.nowcoder.com/practice/0ad8f1c0d7b84c6d8c560298f91d5e66练习2&…

物理AI是什么技术?

当英伟达CEO黄仁勋在链博会上明确提出“物理AI将是AI的下一浪潮”时&#xff0c;这个看似陌生的概念瞬间引发了科技圈的广泛关注。究竟什么是物理AI&#xff1f;它与我们熟悉的人工智能有何不同&#xff1f;又将如何重塑我们与物理世界的交互方式&#xff1f; 物理AI&#xff1…

GRIB数据处理相关指令

GRIB 数据格式简介 GRIB(General Regularly distributed Information in Binary form)&#xff0c;是由世界气象组织&#xff08;WMO&#xff09;设计和维护的一种用于存储和传输网格数据的标准数据格式&#xff0c;它是一种自描述的二进制压缩格式&#xff0c;通常具有扩展名…

微服务学习(六)之分布式事务

微服务学习&#xff08;六&#xff09;之分布式事务一、认识Seata二、部署TC服务1、准备数据库表2、准备配置文件3、docker部署三、微服务集成seata1、引入依赖2、改造配置3、添加数据库表4、测试四、XA模式1、两阶段提交2、seata的XA模型3、优缺点4、实现步骤五、AT模式1、Sea…

Go实现用户登录小程序

写一个用户登录注册的小程序 运行程序&#xff0c;给出提示1. 注册输入用户名、密码、年龄、性别 {"用户名": "root", "passwd": "123456", "age": 18, "sex": "男"}注册前要判断是否存在此用户2. 登录…

鸿蒙蓝牙通信

https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-bluetooth-low-energy 蓝牙权限 module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.ACCESS_BLUETOOTH","reason": "…

Java:Map

文章目录Map常用方法Map遍历的三种方法先获取Map集合的全部键&#xff0c;再通过遍历来找值Entry对象forEach结合lambda表达式Map 案例分析需求我的代码&#xff08;不好&#xff09;老师的代码&#xff08;好&#xff09;好在哪里另外集合分为Collection和MapMap常用方法 代码…

fastjson2 下划线字段转驼峰对象

在对接第三方或查询数据库时&#xff0c;返回的字段是下划线分隔的&#xff0c;而在业务中需要转成java对象&#xff0c;java对象的字段是驼峰的&#xff0c;使用fastjson2时&#xff0c;有两种方法可以实现&#xff1a; 比如数据格式是&#xff1a; {"item_id": &q…

【硬件】蓝牙音频协议

1. 无线音频传输的工作原理 在无线传输的过程中&#xff0c;音源设备首先将MP3、FLAC等音频文件还原为PCM格式。通过蓝牙音频编码转为蓝牙无线传输的文件&#xff0c;发送到音频设备段。将蓝牙无线传输的文件再次还原为PCM格式&#xff0c;之后转为模拟信号并放大&#xff0c;通…

【宇树科技:未来1-3年,机器人可流水线打螺丝】

在第三届中国国际供应链促进博览会上&#xff0c;宇树科技工作人员表示&#xff0c;未来1到3年内&#xff0c;机器人产品有望从单一工业化产品&#xff0c;发展至复合化工业场景&#xff0c;如机器人搬完箱子后&#xff0c;换个 “手” 就能在流水线上打螺丝。在3到10年内&…

Spring AI 1.0版本 + 千问大模型之 文本记忆对话

上篇文章&#xff0c;主要是简单讲解了一下文本对话的功能。由于模型不具备上下文记忆功能&#xff0c;只能一问一答。因此我们需要实现记忆对话功能&#xff0c;这样大模型回答信息才能够更加准确。 1、pom依赖 项目构建就不详细说了&#xff0c;大家可以参考上篇 文本对话 文…

测试学习之——Pytest Day2

一、Pytest配置框架Pytest的配置旨在改变其默认行为&#xff0c;以适应不同的测试需求和项目结构。理解其配置层级和常用参数&#xff0c;是高效使用Pytest的基础。1. 配置的意义与层级配置的本质在于提供一种机制&#xff0c;允许用户根据项目特点、团队规范或特定测试场景&am…

Go-Redis × RediSearch 全流程实践

1. 连接 Redis ctx : context.Background()rdb : redis.NewClient(&redis.Options{Addr: "localhost:6379",Password: "",DB: 0,Protocol: 2, // 推荐 RESP2// UnstableResp3: true, // 若要体验 RESP3 Raw* })2. 准备示例数据 u…

深入理解指针(指针篇2)

在指针篇1我们已经了解了整型指针&#xff0c;当然还有很多其他类型的指针&#xff0c;像字符指针、数组指针、函数指针等&#xff0c;他们都有他们的特别之处&#xff0c;让我们接着学习。1. 指针类型介绍和应用1.1 字符指针变量字符指针变量类型为char*&#xff0c;一般这样使…

Python+Selenium自动化爬取携程动态加载游记

1. 引言 在旅游行业数据分析、舆情监测或竞品研究中&#xff0c;获取携程等平台的游记数据具有重要价值。然而&#xff0c;携程的游记页面通常采用动态加载&#xff08;Ajax、JavaScript渲染&#xff09;&#xff0c;传统的**<font style"color:rgb(64, 64, 64);backg…

ESP8266服务器建立TCP连接失败AT+CIPSTART=“TCP“,“192.168.124.1“,8080 ERROR CLOSED

1.检查服务器端口8081是否开启监听2.检查路由项是否被防火墙拦截方法 1&#xff1a;使用 netsh查看防火墙规则​netsh advfirewall firewall show rule nameall dirout | findstr "8081"如果无输出&#xff0c;说明防火墙未针对该端口设置规则&#xff08;可能默认拦…

Linux 内存管理(2):了解内存回收机制

目录一、透明大页1.1 原理1.2 透明大页的三大优势1.3 透明大页控制接口详解1.4 使用场景与最佳实践1.5 问题排查与监控1.6 与传统大页的对比二、Linux伙伴系统水位机制详解2.1 三种核心水位详解2.2 水位在伙伴系统中的实现2.3 水位触发机制的实际行为2.4 水位关键操作接口2.5 水…