第21讲:动态内存管理

发布时间:2026/10/13 7:05:04

第21讲:动态内存管理 目录为什么要有动态内存分配malloc 和 freecalloc 和 realloc常见的动态内存的错误动态内存经典笔试题分析柔性数组总结 C/C 中程序内存区域划分正文开始1. 为什么要有动态内存分配我们已经掌握的内存开辟方式有intval20;// 在栈空间上开辟四个字节chararr[10]{0};// 在栈空间上开辟 10 个字节的连续空间但是上述的开辟空间的方式有两个特点空间开辟大小是固定的。数组在声明的时候必须指定数组的长度数组空间一旦确定了大小不能调整。但是对于空间的需求不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道那数组的编译时开辟空间的方式就不能满足了。C 语言引入了动态内存开辟让程序员自己可以申请和释放空间就比较灵活了。2.malloc和free2.1mallocC 语言提供了一个动态内存开辟的函数void*malloc(size_tsize);这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。如果开辟成功则返回一个指向开辟好空间的指针。如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。返回值的类型是void*所以malloc函数并不知道开辟空间的类型具体在使用的时候使用者自己来决定。如果参数size为 0malloc的行为是标准未定义的取决于编译器。2.2freeC 语言提供了另外一个函数free专门是用来做动态内存的释放和回收的函数原型如下voidfree(void*ptr);free函数用来释放动态开辟的内存。如果参数ptr指向的空间不是动态开辟的那么free函数的行为是未定义的。如果参数ptr是NULL指针则函数什么事都不做。malloc和free都声明在stdlib.h头文件中。举个例子#includestdio.h#includestdlib.hintmain(){intnum0;scanf(%d,num);intarr[num]{0};int*ptrNULL;ptr(int*)malloc(num*sizeof(int));if(NULL!ptr)// 判断 ptr 指针是否为空{inti0;for(i0;inum;i){*(ptri)0;}}free(ptr);// 释放 ptr 所指向的动态内存ptrNULL;// 是否有必要return0;}3.calloc和realloc3.1callocC 语言还提供了一个函数叫calloccalloc函数也用来动态内存分配。原型如下void*calloc(size_tnum,size_tsize);函数的功能是为num个大小为size的元素开辟一块空间并且把空间的每个字节初始化为 0。与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全 0。举个例子#includestdio.h#includestdlib.hintmain(){int*p(int*)calloc(10,sizeof(int));if(NULL!p){inti0;for(i0;i10;i){printf(%d ,*(pi));}}free(p);pNULL;return0;}输出结果0 0 0 0 0 0 0 0 0 0所以如果我们对申请的内存空间的内容要求初始化那么可以很方便地使用calloc函数来完成任务。3.2reallocrealloc函数的出现让动态内存管理更加灵活。有时候我们发现过去申请的空间太小了有时候我们又会觉得申请的空间过大了。为了合理地使用内存我们一定会对内存的大小做灵活的调整那么realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下void*realloc(void*ptr,size_tsize);ptr是要调整的内存地址。size是调整之后的新大小。返回值为调整之后的内存起始位置。这个函数在调整原内存空间大小的基础上还会将原来内存中的数据移动到新的空间。realloc在调整内存空间时存在两种情况情况 1原有空间之后有足够大的空间。情况 2原有空间之后没有足够大的空间。情况 1当是情况 1 的时候要扩展内存就直接在原有内存之后追加空间原来空间的数据不发生变化。情况 2当是情况 2 的时候原有空间之后没有足够多的空间时扩展的方法是在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。由于上述的两种情况realloc函数的使用就要注意一些。#includestdio.h#includestdlib.hintmain(){int*ptr(int*)malloc(100);if(ptr!NULL){// 业务处理}else{return1;}// 扩展容量// 代码 1 - 直接将 realloc 的返回值放到 ptr 中ptr(int*)realloc(ptr,1000);// 这样可以吗如果申请失败会如何// 代码 2 - 先将 realloc 函数的返回值放在 p 中不为 NULL再放到 ptr 中int*pNULL;prealloc(ptr,1000);if(p!NULL){ptrp;}// 业务处理free(ptr);return0;}4. 常见的动态内存的错误4.1 对NULL指针的解引用操作voidtest(){int*p(int*)malloc(INT_MAX/4);*p20;// 如果 p 的值是 NULL就会有问题free(p);}4.2 对动态开辟空间的越界访问voidtest(){inti0;int*p(int*)malloc(10*sizeof(int));if(NULLp){exit(EXIT_FAILURE);}for(i0;i10;i){*(pi)i;// 当 i 是 10 的时候越界访问}free(p);}4.3 对非动态开辟内存使用free释放voidtest(){inta10;int*pa;free(p);// ok?}4.4 使用free释放一块动态开辟内存的一部分voidtest(){int*p(int*)malloc(100);p;free(p);// p 不再指向动态内存的起始位置}4.5 对同一块动态内存多次释放voidtest(){int*p(int*)malloc(100);free(p);free(p);// 重复释放}4.6 动态开辟内存忘记释放内存泄漏voidtest(){int*p(int*)malloc(100);if(NULL!p){*p20;}}intmain(){test();while(1);}忘记释放不再使用的动态开辟的空间就会造成内存泄漏。切记动态开辟的空间一定要释放并且正确释放。5. 动态内存经典笔试题分析5.1 题目 1voidGetMemory(char*p){p(char*)malloc(100);}voidTest(void){char*strNULL;GetMemory(str);strcpy(str,hello world);printf(str);}请问运行Test函数会有什么样的结果5.2 题目 2char*GetMemory(void){charp[]hello world;returnp;}voidTest(void){char*strNULL;strGetMemory();printf(str);}请问运行Test函数会有什么样的结果5.3 题目 3voidGetMemory(char**p,intnum){*p(char*)malloc(num);}voidTest(void){char*strNULL;GetMemory(str,100);strcpy(str,hello);printf(str);}请问运行Test函数会有什么样的结果5.4 题目 4voidTest(void){char*str(char*)malloc(100);strcpy(str,hello);free(str);if(str!NULL){strcpy(str,world);printf(str);}}请问运行Test函数会有什么样的结果6. 柔性数组也许你从来没有听说过**柔性数组flexible array**这个概念但是它确实是存在的。C99 中结构中的最后一个元素允许是未知大小的数组这就叫做“柔性数组”成员。例如typedefstructst_type{inti;inta[0];// 柔性数组成员}type_a;有些编译器会报错无法编译可以改成typedefstructst_type{inti;inta[];// 柔性数组成员}type_a;6.1 柔性数组的特点结构中的柔性数组成员前面必须至少有一个其他成员。sizeof返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc()函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小。例如typedefstructst_type{inti;inta[0];// 柔性数组成员}type_a;intmain(){printf(%d | ,sizeof(type_a));// 输出的是 4return0;}6.2 柔性数组的使用// 代码 1#includestdio.h#includestdlib.hintmain(){inti0;type_a*p(type_a*)malloc(sizeof(type_a)100*sizeof(int));// 业务处理p-i100;for(i0;i100;i){p-a[i]i;}free(p);return0;}这样柔性数组成员a相当于获得了 100 个整型元素的连续空间。6.3 柔性数组的优势上述的type_a结构也可以设计为下面的结构也能完成同样的效果。// 代码 2#includestdio.h#includestdlib.htypedefstructst_type{inti;int*p_a;}type_a;intmain(){inti0;type_a*p(type_a*)malloc(sizeof(type_a));p-i100;p-p_a(int*)malloc(p-i*sizeof(int));// 业务处理for(i0;i100;i){p-p_a[i]i;}// 释放空间free(p-p_a);p-p_aNULL;free(p);pNULL;return0;}上述代码 1和代码 2可以完成同样的功能但是方法 1的实现有两个好处**第一个好处是**方便内存释放如果我们的代码是在一个给别人用的函数中你在里面做了二次内存分配并把整个结构体返回给用户。用户调用free可以释放结构体但是用户并不知道这个结构体内的成员也需要free所以你不能指望用户来发现这个事。所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存也给释放掉。第二个好处是这样有利于访问速度。连续的内存有益于提高访问速度也有益于减少内存碎片。其实我个人觉得也没多高了反正你跑不了要用做偏移量的加法来寻址扩展阅读C 语言结构体里的数组和指针7. 总结 C/C 中程序内存区域划分C/C 程序内存分配的几个区域栈区stack在执行函数时函数内局部变量的存储单元都可以在栈上创建函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中效率很高但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。堆区heap一般由程序员分配释放若程序员不释放程序结束时可能由 OS 回收。分配方式类似于链表。数据段静态区static存放全局变量、静态数据。程序结束后由系统释放。代码段存放函数体类成员函数和全局函数的二进制代码。
延伸阅读

更多相关文章

2026/10/13 7:00:03

恶意代码概述

1. 计算机病毒(Virus)核心特点:必须依附文件,需要人为触发,不能自己网上传播 原理:寄生在正常文件里(exe / 文档)。必须用户手动打开这个带毒文件,病毒才会激活&#xff…

2026/10/13 7:00:03

数字游民的移动办公方案:手机也能随时访问和编辑电脑文件

一、数字游民的「设备困境」 数字游民(Digital Nomad)的生活方式听起来很酷:边旅行边工作,今天在清迈的咖啡厅,下周在巴厘岛的共享办公空间。 但实际工作中有一个持续存在的痛点:你的文件不跟你一起旅行。 …

2026/10/13 9:20:28

SQL字符串精准定位与条件填充实战:POSITION+SUBSTRING+CASE WHEN

1. 这不是“查数据”,而是“精准定位智能填充”的组合拳SQL里最常被低估的,其实是字段级的字符串操作能力。很多人一看到“从指定字段查找指定字符”,第一反应是LIKE加%模糊匹配——这就像用消防水枪给盆栽浇水,方向对了&#xff…

2026/10/13 9:20:28

Python+MySQL学生选课管理系统:从数据库设计到答辩避坑全指南

简介:基于Python和MySQL实现的学生选课管理系统,是一份面向计算机相关专业期末大作业或毕业设计的完整项目包。项目由导师指导并通过高分评审,源码均经过本地编译调试,可稳定运行,适合需要快速构建课程设计或提升实战能…

2026/10/13 9:20:28

COMSOL激光熔覆与选区熔融仿真:从热源标定到残余应力评估

做激光熔覆和选区熔融仿真的这些年,我被问得最多的一句话是——“COMSOL到底能不能把这两个工艺算清楚?”我的回答通常是一半肯定、一半劝退:能,但前提是你得把热源标定、材料参数温度相关性、相变潜热、单元激活、熔池对流这几件…

2026/10/13 9:15:26

Kvaesitso 崩溃与 Bug 反馈指南:从 Crash Reporter 报告到问题修复

移动开发 【免费下载链接】Kvaesitso A search-focused Android launcher 项目地址: https://gitcode.com/gh_mirrors/kv/Kvaesitso 点击查看 免费下载 Kvaesitso 是一个以搜索为核心的开源 Android 桌面启动器,其官方贡献指南中的《Report Bugs》专门面…

2026/10/13 5:19:27

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/13 0:04:41

基于Triton部署YOLO目标检测:从训练脚本到线上服务

简介:这份资源面向希望将目标检测模型落地到生产环境的深度学习开发者与算法工程师,聚焦于用 Triton Inference Server 部署 YOLOv9 的完整实战方案,解决从训练权重到线上推理服务之间的工程化难题。压缩包共 16 个文件,约 886KB&…

2026/10/13 0:04:41

手把手教你云端部署Qwen-Image-2.1:从GPU选型到在线文生图服务

Qwen-Image-2.1 这个开源的图像生成模型,最近在内容创作和 AI 应用圈子里热度相当高。它属于通义 Qwen 系列里的视觉生成分支,让我眼前一亮的是它对中文长文本的理解能力——以前用那些老方案生成带中文文字的图,十个里九个是“乱码印刷体”&…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑