C 语言进【进阶篇】之动态内存管理:从底层机制到实战优化

目录

  • 🚀前言
  • 🌟动态内存分配的必要性
  • 🤔动态内存分配函数深度剖析
    • 💯malloc函数:内存申请的主力军
    • 💯free函数:释放内存的“清道夫”
    • 💯calloc函数:初始化内存的利器
    • 💯realloc函数:灵活调整内存大小的“魔术师”
  • 🐍常见的动态内存错误及避免方法
  • ✍️柔性数组:独特而强大的内存管理工具
  • ⚙️C/C++中程序内存区域划分:深入理解内存布局
  • 🧑‍🎓总结:掌握动态内存管理,提升编程能力

🚀前言

大家好!我是 EnigmaCoder。本文收录于我的专栏 C,感谢您的支持!

  • 在C语言编程的广袤天地中,内存管理堪称核心支柱之一,它对程序的性能、稳定性起着决定性作用。熟练掌握动态内存管理技巧,是从编程新手迈向高手的必经之路。今天,就让我们一同深入探寻C语言动态内存管理的奥秘。

🌟动态内存分配的必要性

在C语言里,常规的内存开辟方式有其局限性。像定义普通变量int val = 20;,它会在栈空间占用4个字节;定义数组char arr[10] = {0};,则在栈空间开辟10个字节的连续区域。这种方式的弊端在于空间大小固定,数组一经声明长度就无法更改。但实际编程时,很多场景下所需的内存空间大小要在程序运行阶段才能确定。比如开发一个学生成绩管理系统,在录入成绩前,根本不知道会有多少学生,这时候常规的内存开辟方式就难以满足需求,动态内存分配则能有效解决这类问题,让程序根据实际情况灵活申请和释放内存。

🤔动态内存分配函数深度剖析

💯malloc函数:内存申请的主力军

malloc函数用于向系统申请一块连续的可用内存空间,其函数原型为void* malloc (size_t size); 。若申请成功,它会返回一个指向该内存空间的指针;若失败,就返回NULL指针。由于返回值是void*类型,在使用时需进行强制类型转换,明确所指向的数据类型。特别要注意,当参数size为0时,malloc的行为在标准中未定义,不同编译器的处理方式可能不同。

#include <stdio.h>
#include <stdlib.h>int main() {int num;printf("请输入要开辟的整数个数: ");scanf("%d", &num);int* ptr = (int*)malloc(num * sizeof(int));if (ptr == NULL) {printf("内存分配失败,原因可能是系统内存不足或其他错误。\n");return 1;}for (int i = 0; i < num; i++) {ptr[i] = i;}for (int i = 0; i < num; i++) {printf("%d ", ptr[i]);}free(ptr);ptr = NULL;return 0;
}

在这段代码中,先根据用户输入的整数个数num,使用malloc申请相应大小的内存空间。申请后,立即检查返回的指针是否为NULL,若为NULL,则输出错误信息并终止程序。之后对申请的内存进行赋值和遍历输出操作,最后使用free释放内存,并将指针置为NULL,防止出现野指针。

💯free函数:释放内存的“清道夫”

free函数专门用于释放动态开辟的内存,其函数原型为void free (void* ptr); 。若ptr指向的不是动态开辟的内存,调用free函数会导致未定义行为;若ptrNULL指针,函数则不执行任何操作。在实际编程中,正确使用free函数释放不再使用的内存,是避免内存泄漏的关键。

💯calloc函数:初始化内存的利器

calloc函数同样用于动态内存分配,原型是void* calloc (size_t num, size_t size); 。它的独特之处在于,会为num个大小为size的元素开辟一块内存空间,并将每个字节初始化为0。这在需要初始化内存的场景中,如创建用于存储数据的数组且初始值都为0时,使用calloc会非常方便。

#include <stdio.h>
#include <stdlib.h>int main() {int *p = (int*)calloc(5, sizeof(int));if (p == NULL) {printf("内存分配失败,可能是内存不足。\n");return 1;}for (int i = 0; i < 5; i++) {printf("%d ", p[i]);}free(p);p = NULL;return 0;
}

此代码通过calloc为5个int类型的元素申请内存空间,并自动将每个元素初始化为0,然后进行遍历输出,最后释放内存。

💯realloc函数:灵活调整内存大小的“魔术师”

realloc函数为动态内存管理带来了极大的灵活性,可用于调整已动态开辟内存的大小。其函数原型为void* realloc (void* ptr, size_t size);ptr是要调整的内存地址,size是调整后的新大小,返回值为调整后的内存起始位置。在调整内存大小时,存在两种情况:如果原有空间之后有足够大的空间,直接在原有内存后追加空间,原有数据保持不变;若原有空间之后空间不足,则会在堆空间中另找一块合适大小的连续空间,此时函数返回新的内存地址,原有数据会被复制到新空间。

#include <stdio.h>
#include <stdlib.h>int main() {int *ptr = (int*)malloc(5 * sizeof(int));if (ptr == NULL) {printf("内存分配失败,请检查系统内存状态。\n");return 1;}for (int i = 0; i < 5; i++) {ptr[i] = i;}int *new_ptr = (int*)realloc(ptr, 10 * sizeof(int));if (new_ptr == NULL) {printf("内存调整失败,可能无法找到足够大的连续内存空间。\n");free(ptr);return 1;}ptr = new_ptr;for (int i = 5; i < 10; i++) {ptr[i] = i;}for (int i = 0; i < 10; i++) {printf("%d ", ptr[i]);}free(ptr);return 0;
}

这段代码先使用malloc申请了5个int类型的内存空间,然后尝试使用realloc将其扩展为10个int类型的空间。在扩展过程中,仔细检查realloc的返回值,确保内存调整成功。若调整失败,释放原有的内存空间并输出错误信息。

🐍常见的动态内存错误及避免方法

  1. 对NULL指针的解引用操作:当malloc因内存不足等原因返回NULL时,如果直接对返回的指针进行解引用操作,如*p = 20;,程序会发生严重错误,甚至崩溃。为避免这种情况,在使用malloc返回的指针前,一定要检查其是否为NULL
  2. 对动态开辟空间的越界访问:在访问动态分配的数组或内存块时,如果超出了分配的范围,就会发生越界访问。这会导致程序出现未定义行为,可能当时不会报错,但后续会引发各种难以排查的问题。编写代码时,务必严格控制访问边界。
  3. 对非动态开辟内存使用free释放free函数只能用于释放动态开辟的内存。若对非动态开辟的内存(如普通局部变量的地址)使用free,会导致程序出现不可预测的错误。在调用free前,要确保所释放的内存是通过动态分配获得的。
  4. 使用free释放一块动态开辟内存的一部分free函数必须释放动态内存的起始地址,若释放的是动态内存中间的某个位置,会破坏内存管理机制,导致程序出错。
  5. 对同一块动态内存多次释放:重复释放同一块动态内存会使内存管理系统混乱,通常会导致程序崩溃。为避免这种情况,释放内存后,应及时将指针置为NULL,防止再次误释放。
  6. 动态开辟内存忘记释放(内存泄漏):当动态开辟的内存不再使用,但未调用free释放时,就会发生内存泄漏。随着程序的运行,内存泄漏会不断积累,导致系统内存逐渐减少,最终影响系统性能甚至使程序崩溃。养成良好的编程习惯,及时释放不再使用的动态内存至关重要。

✍️柔性数组:独特而强大的内存管理工具

C99标准引入了柔性数组这一特殊概念,它允许结构体中的最后一个元素是未知大小的数组。柔性数组具有两个重要特点:一是结构体中的柔性数组成员前面必须至少有一个其他成员;二是sizeof返回的结构体大小不包括柔性数组的内存。

#include <stdio.h>
#include <stdlib.h>typedef struct st_type {int i;int a[]; 
} type_a;int main() {type_a *p = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));p->i = 100;for (int i = 0; i < 5; i++) {p->a[i] = i;}for (int i = 0; i < 5; i++) {printf("%d ", p->a[i]);}free(p);return 0;
}

在上述代码中,先定义了包含柔性数组的结构体type_a,然后使用malloc为结构体及其柔性数组分配内存空间。分配时,要确保分配的内存大小大于结构体本身的大小,以满足柔性数组的预期大小。之后对柔性数组进行赋值和遍历输出操作,最后释放整个结构体的内存。

柔性数组的优势明显。一方面,方便内存释放。当在函数中使用柔性数组并返回结构体指针时,用户只需调用一次free,就能释放结构体及其柔性数组所占用的全部内存,无需额外处理。另一方面,它有利于提高访问速度,因为柔性数组的内存是连续分配的,连续的内存访问效率更高,同时也能减少内存碎片的产生。

⚙️C/C++中程序内存区域划分:深入理解内存布局

C/C++程序的内存主要分为以下几个区域:

  1. 栈区(stack):在函数执行过程中,函数内的局部变量、函数参数、返回数据和返回地址等都存放在栈区。栈内存的分配和释放由系统自动管理,函数执行结束时,栈上的存储单元会自动释放。栈区的内存分配效率高,但容量有限。
  2. 堆区(heap):一般由程序员手动分配和释放。若程序员未释放,程序结束时可能由操作系统回收。堆区的分配方式类似于链表,适合用于动态内存分配,如malloccallocrealloc等函数分配的内存都来自堆区。
  3. 数据段(静态区):用于存放全局变量和静态数据。程序运行期间,这些数据一直存在,程序结束后由系统释放。
  4. 代码段:存放函数体(包括类成员函数和全局函数)的二进制代码,是只读的,用于存储程序的可执行指令。

高地址
┌───────────────┐
│ 内核空间 │
├───────────────┤
│ 栈区(向下增长)│
├───────────────┤
│ 内存映射段 │
├───────────────┤
│ 堆区(向上增长)│
├───────────────┤
│ 数据段(静态区)│
├───────────────┤
│ 代码段 │
└───────────────┘
低地址

理解这些内存区域的划分,有助于我们更好地规划和管理程序内存,避免因内存使用不当导致的错误。

🧑‍🎓总结:掌握动态内存管理,提升编程能力

  • C语言的动态内存管理是一个功能强大且复杂的领域。通过mallocfreecallocrealloc等函数,我们能够灵活地申请、释放和调整内存空间,满足各种复杂的编程需求。然而,在使用过程中,必须时刻警惕常见的动态内存错误,如对NULL指针的解引用、越界访问、内存泄漏等,养成良好的编程习惯,确保程序的稳定性和可靠性。
  • 柔性数组作为C语言的一个独特特性,为我们提供了一种高效的内存管理方式,在合适的场景下使用可以显著提升程序性能。同时,深入理解C/C++程序的内存区域划分,能让我们从宏观层面把握内存的使用,优化程序的内存布局。

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

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

相关文章

2023华东师范大学计算机复试上机真题

2023华东师范大学计算机复试上机真题 2022华东师范大学计算机复试上机真题 2021华东师范大学计算机复试上机真题 2023华东师范大学计算机复试机试真题 2022华东师范大学计算机复试机试真题 2021华东师范大学计算机复试机试真题 在线评测&#xff1a;传送门&#xff1a;pgcode.…

Mac下安装Zed以及Zed对MCP(模型上下文协议)的支持

Zed是当前新流行的一种编辑器&#xff0c;支持MCP&#xff08;模型上下文协议&#xff09; Mac下安装Zed比较简单&#xff0c;直接有安装包&#xff0c;在这里&#xff1a; brew install --cask zedMac Monterey下是可以安装上的&#xff0c;亲测有效。 配置 使用CtrlShiftP…

Spring Boot 约定大于配置:实现自定义配置

文章目录 Spring Boot 约定大于配置&#xff1a;实现自定义配置引言1. Spring Boot 的约定大于配置2. 自定义配置的需求3. 实现自定义配置的步骤4. 示例&#xff1a;自定义 Spring MVC 配置4.1 创建自定义配置类4.2 创建自定义拦截器4.3 测试自定义配置 5. 其他自定义配置场景5…

交易系统优化方案

证券交易系统优化方案 一、选股策略体系 (一)择时策略矩阵 尾盘集中筛选法(14:30后)聚焦量价异动个股,捕捉次日溢价机会早盘转债套利法(9:25-10:00)通过可转债与正股联动性捕捉日内机会龙头战法(全时段)行业板块强度排序,锁定前三大市值龙头容量资金战法(中盘股适用…

在线Doc/Docx转换为PDF格式 超快速转换的一款办公软件 文档快速转换 在线转换免费转换办公软件

小白工具https://www.xiaobaitool.net/files/word-pdf/提供了一项非常实用的在线服务——将Doc或Docx格式的文档快速转换为PDF格式。这项服务不仅操作简单&#xff0c;而且转换效率高&#xff0c;非常适合需要频繁处理文档转换的用户。 服务特点&#xff1a; 批量转换&#x…

java学习总结(四)MyBatis多表

一、多表结构 学生表、班级表、课程表、班级课程表 二、一对一 一个学生只属于一个班级。 查询&#xff1a;id name age gender banjiName SELECT s.id,s.name,s.age,s.gender,b.id AS banjiId,b.name AS banjiName FROM student AS s INNER JOIN banji AS b ON s.banji_id…

大语言模型学习及复习笔记(1)语言模型的发展历程

1.大模型进入人们视野 ChatGPT 于2022年11月底上线 模型名称 发布时间 核心突破 GPT-3 2020年6月 首款千亿参数模型&#xff0c;少样本学习 GPT-3.5-Turbo 2022年11月 对话能力优化&#xff0c;用户级应用落地 GPT-4 2023年3月 多模态、强逻辑推理 GPT-4o / GPT-4…

【NLP】 3. Distributional Similarity in NLP(分布式相似性)

Distributional Similarity in NLP&#xff08;分布式相似性&#xff09; 分布式相似性&#xff08;Distributional Similarity&#xff09; 是自然语言处理&#xff08;NLP&#xff09;中的核心概念&#xff0c;基于“相似的单词出现在相似的上下文中”这一假设。它用于衡量单…

【C#学习】协程等待

来源GPT&#xff0c;仅记录学习 yield return WaitForEndOfFrame() 适用于 渲染结束后再执行代码&#xff0c;但 WebGL 可能不适合这个操作&#xff0c;会拖慢帧率。(渲染得太慢&#xff09; yield return null; 让代码在下一帧的 Update() 里继续运行&#xff0c;更加流畅。 …

【项目】负载均衡式在线OJ

负载均衡式在线OJ 目录 负载均衡式在线OJ 1.项目介绍&#xff1a; 2.comm 2.1 log.hpp 日志等级 开放式日志 时间戳工具 2.2 util.hpp TimeUtil类 PathUtil类 FileUtil类 StringUtil类 3.Compile_server 3.1compile_run.hpp RemoveTempFile CodeToDesc Start 3.…

实现“XXX一张图“进行环境设施设备可视化管理

实现“电网一张图”、“铁路一张图”、“水库一张图”、“森林一张图”等概念,本质上是将某一领域的空间数据、设施设备、运行状态等信息整合到一个统一的数字化平台上,实现全域可视化、智能化管理和协同运营。这种“一张图”模式依赖于地理信息系统(GIS)、物联网(IoT)、…

《基於Python的网络爬虫抓包技术研究与应用》

## 摘要 本文探讨了基于Python的网络爬虫抓包技术及其应用。随着互联网数据的快速增长&#xff0c;网络爬虫技术在数据采集和分析中扮演着越来越重要的角色。本研究首先介绍了网络爬虫的基本概念和Python在爬虫开发中的优势&#xff0c;然后深入分析了抓包技术的原理和常用工具…

【蓝桥杯速成】| 1.暴力解题

1高频考点与暴力解题_哔哩哔哩_bilibili 感谢up主分享&#xff0c;以下内容是学习笔记&#xff0c;以c为主&#xff0c;部分python 题目一&#xff1a;维纳的年龄 题目内容 美国数学家维纳(N.Wiener)智力早熟&#xff0c; 11岁就上了大学。他曾在1935~1936年应邀来中国清华大…

[C++Qt] 槽函数收不到信号问题(信号的注册)

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

从零开始用AI开发游戏(一)

1. 核心玩法设计 核心目标&#xff1a;玩家需在随机生成的3D迷宫中寻找出口&#xff0c;躲避陷阱、收集道具、解开谜题。核心机制&#xff1a; 随机生成迷宫&#xff1a;每次游戏生成不同结构的迷宫&#xff08;递归分割算法或深度优先搜索&#xff09;。第一人称视角&#xf…

基于ssm的宠物医院信息管理系统(全套)

一、系统架构 前端&#xff1a;html | layui | vue | element-ui 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat | idea | nodejs 二、代码及数据库 三、功能介绍 01. web端-首页1 02. web端-首页…

【CXX】6.7 SharedPtr<T> — std::shared_ptr<T>

std::shared_ptr 的 Rust 绑定称为 SharedPtr。 限制&#xff1a; SharedPtr 不支持 T 为不透明的 Rust 类型。对于在语言边界上传递不透明 Rust 类型的所有权&#xff0c;应改用 Box&#xff08;C 中的 rust::Box&#xff09;。 示例 // src/main.rsuse std::ops::Deref; …

利用python生成excel中模板范围对应的shape文件

利用python生成excel中模板范围对应的shape文件 # -*- coding: utf-8 -*- import os.pathimport pandas as pd from shapely.geometry import Polygon from shapely.wkt import dumps import argparse# 创建解析器 parser argparse.ArgumentParser(description"这是一个…

cursor使用

引入私有文档 设置-> Features->下滑找到Docs url后边多加一个 / 可以拉取url下所有的页面(子页面&#xff0c;子目录)&#xff0c;不加只拉取url当前页面 使用 选择 Docs 回车 选择 文档 直接解析链接 链接 回车 搜索引擎 web 对比git版本差异 git 选择其中一个 g…

达梦数据库中插入导出图片的方法与应用

达梦数据库中插入导出图片的方法与应用 在数据库的实际应用场景中&#xff0c;图片存储是一项常见且重要的需求。以电商平台为例&#xff0c;商品展示图片是吸引消费者的关键元素&#xff1b;而在社交软件里&#xff0c;用户头像更是个人形象的直观体现。针对达梦数据库&#…