学C的第三十二天【动态内存管理】
=========================================================================
相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com)
=========================================================================
接上期:
学C的第三十一天【通讯录的实现】_高高的胖子的博客-CSDN博客
=========================================================================
1 . 为什么存在动态内存分配
学到现在认识的内存开辟方式有两种:
- 创建变量:
int val = 20; —— 在栈空间上开辟4个字节
- 创建数组:
char arr[10] = {10}; —— 在栈空间上开辟10个字节的连续空间
上述的开辟空间的方式有两个特点:
- 空间开辟大小是固定的。
- 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。
有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态内存开辟了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 . 动态内存函数的介绍
(1). malloc 和 free :
malloc :
该函数是一个动态内存开辟的函数,
这个函数可以向内存申请一块连续可用的空间,
并返回指向这块空间的指针。
书写格式如下:
void* malloc (size_t size);
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,
- 具体在使用的时候使用者自己决定。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
- malloc声明在 stdlib.h 头文件中。
示例:
free :
malloc函数申请的内存空间在程序退出时才会还给操作系统,
如果程序不退出,动态申请的内存是不会主动释放的。
所以需要 free函数 来释放动态内存。
书写格式如下:
void free (void* ptr);
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- free声明在 stdlib.h 头文件中。
示例:
(2). calloc :
书写格式如下:
void* calloc (size_t num, size_t size);
- 函数的功能是以 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
- calloc 声明在 stdlib.h 头文件中。
示例:
(3). realloc :
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,
那为了合理地使用内存,我们一定会对内存的大小做灵活的调整。
那 realloc 函数就可以做到对动态开辟内存大小的调整。
书写格式如下:
void* realloc (void* ptr, size_t size);
- ptr 是要调整的内存地址,如果填的是NULL空指针,那会开辟一块新的空间,跟malloc函数一样。
- size 是调整之后新大小
- 返回值 为调整之后的内存起始位置。
- 这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
- realloc在调整内存空间的是存在两种情况:
情况1 -- 原有空间之后有足够大的空间:
在这种情况下,要扩展内存就直接在原有内存之后直接追加空间,
原来空间的数据不发生变化。
情况2 -- 原有空间之后没有足够大的空间:
在这种情况下,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,
将旧的空间中的数据拷贝到新的空间中,再释放旧的空间,最后返回新空间的起始地址。
这样函数返回的就是一个新的内存地址。
- realloc 声明在 stdlib.h 头文件中。
示例:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 . 常见的动态内存错误
(1). 对NULL指针的解引用操作:
malloc、calloc、realloc函数 都可能开辟空间,
开辟空间就有可能会失败,返回 NULL空指针,
这时解引用该空指针就可能会出问题。示例:
(2). 对动态开辟空间的越界访问:
示例:
(3). 对非动态开辟内存使用free函数释放:
示例:
(4). 使用free函数释放一块动态开辟内存的一部分:
使用动态空间过程中,
改变了指向动态空间的指针,
这时要使用free函数释放空间就会出问题。
示例:
(5). 对同一块动态内存多次释放:
可以在释放动态空间后,
将该空间指针设置为空指针,
防止多次释放。
示例:
(6). 动态开辟内存忘记释放 -- 内存泄漏 :
只有两种方式可以对动态内存进行释放:
free函数 和 程序运行结束,
所以如果 忘记释放 或 没释放且程序无法结束,
就会造成内存泄漏。
示例:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 . 相关经典笔试题
题一:
进行修改:
对应代码:
//1:改前 #include <stdio.h> #include <stdlib.h> #include <string.h>void GetMemory(char* p) {//开辟动态空间p = (char*)malloc(100); }void Test(void) {//创建空指针:char* str = NULL;//使用该指针进行动态内存开辟:GetMemory(str);//对动态空间赋值并使用:strcpy(str, "hello world");printf(str);}int main() {Test();return 0; }//1:改后 #include <stdio.h> #include <stdlib.h> #include <string.h>void GetMemory(char** p) {//开辟动态空间*p = (char*)malloc(100); }void Test(void) {//创建空指针:char* str = NULL;//使用该指针进行动态内存开辟:GetMemory(&str);//对动态空间赋值并使用:strcpy(str, "hello world");printf(str);//使用后进行释放:free(str);str = NULL; }int main() {Test();return 0; }
题二:
进行修改:
对应代码:
//2:改前 #include <stdio.h> #include <stdlib.h>char* GetMemory(void) {char p[] = "hello world";return p; }void Test(void) {//创建空指针:char* str = NULL;//调用上面的函数:str = GetMemory();printf(str); }int main() {Test();return 0; }//2:改后 #include <stdio.h> #include <stdlib.h>char* GetMemory(void) {static char p[] = "hello world";return p; }void Test(void) {//创建空指针:char* str = NULL;str = GetMemory();printf(str); }int main() {Test();return 0; }
题三:
进行修改:
对应代码:
//3:改前: #include <stdio.h> #include <stdlib.h>void GetMemory(char** p, int num) {//根据需求创建动态空间:*p = (char*)malloc(num); }void Test(void) {//创建空指针变量:char* str = NULL;//调用函数:GetMemory(&str, 100);//使用动态空间:strcpy(str, "hello");printf(str); }int main() {Test();return 0; }//3:改后: #include <stdio.h> #include <stdlib.h>void GetMemory(char** p, int num) {//根据需求创建动态空间:*p = (char*)malloc(num); }void Test(void) {//创建空指针变量:char* str = NULL;//调用函数:GetMemory(&str, 100);//使用动态空间:strcpy(str, "hello");printf(str);//释放:free(str);str = NULL; }int main() {Test();return 0; }
题四:
进行修改:
对应代码:
//4:改前: #include <stdio.h> #include <stdlib.h>void Test(void) {//创建动态空间并接收:char* str = (char*)malloc(100);//使用动态空间:strcpy(str, "hello");//释放:free(str);if (str != NULL){strcpy(str, "world");printf(str);} }int main() {Test();return 0; }//4:改后: #include <stdio.h> #include <stdlib.h>void Test(void) {//创建动态空间并接收:char* str = (char*)malloc(100);//使用动态空间:strcpy(str, "hello");//释放:free(str);str = NULL;if (str != NULL){strcpy(str, "world");printf(str);} }int main() {Test();return 0; }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 . C/C++程序的内存开辟
(1). C/C++程序内存区域划分:
C/C++程序内存分配的几个区域:
1. 栈区(stack):
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,
函数执行结束时这些存储单元自动被释放。
栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区(heap):
一般由程序员分配释放, 若程序员不释放,
程序结束时可能由OS(操作系统)回收 。
分配方式类似于链表。
3. 数据段(静态区)(static)
存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:
存放函数体(类成员函数和全局函数)的二进制代码。
图示:
有了这幅图,
我们就可以更好的理解static关键字修饰局部变量的例子了。
实际上普通的局部变量是在栈区分配空间的,
栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存放在数据段(静态区),
数据段的特点是在上面创建的变量,直到程序结束才销毁,
所以生命周期变长。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 . 柔性数组
C99中,结构体中最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
实例:
(1). 柔性数组的特点:
- 结构体中的柔性数组成员前面必须至少有一个其他成员。
- sizeof 返回的这种结构体大小不包括柔性数组的内存。
- 包含柔性数组成员的结构体用malloc ()函数进行内存的动态分配,
并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
实例:
(2). 柔性数组的使用:
实例:
(3). 柔性数组的优势:
方便内存释放
如果我们的代码是在一个给别人用的函数中,
你在里面做了二次内存分配(使用两次malloc函数可以实现类似柔性数组的效果),
并把整个结构体返回给用户,
用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,
所以你不能指望用户来发现这个事。
所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,
并返回给用户一个结构体指针,
用户做一次free就可以把所有的内存也给释放掉。
有利于访问速度
连续的内存有益于提高访问速度,
也有益于减少内存碎片(两个开辟的空间中间空余的内存)。
相关文章:

学C的第三十二天【动态内存管理】
相关代码gitee自取:C语言学习日记: 加油努力 (gitee.com) 接上期: 学C的第三十一天【通讯录的实现】_高高的胖子的博客-CSDN博客 1 . 为什么存在动态内存分配 学到现在认识的内存开辟方式有两种: 创建变量: int val …...
聊聊elasticsearch的data-streams
序 本文主要研究一下elasticsearch的data-streams data-streams 主要特性 首先data streams是由一个或者多个自动生成的隐藏索引组成的,它的格式为.ds-<data-stream>-<yyyy.MM.dd>-<generation> 示例.ds-web-server-logs-2099.03.07-000034&a…...
unreal engine c++ 创建tcp server, tcp client
TCP客户端 TcpConnect.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "Common/UdpSocketReceiver.h" #include "GameFramework/Actor.h"DECLARE_DELEGATE…...

24届华东理工大学近5年自动化考研院校分析
今天给大家带来的是华东理工大学控制考研分析 满满干货~还不快快点赞收藏 一、华东理工大学 学校简介 华东理工大学原名华东化工学院,1956年被定为全国首批招收研究生的学校之一,1960年起被中共中央确定为教育部直属的全国重点大学&#…...

初识集合和背后的数据结构
目录 集合 Java集合框架 数据结构 算法 集合 集合,是用来存放数据的容器。其主要表现为将多个元素置于一个单元中,用于对这些元素进行增删查改。例如,一副扑克牌(一组牌的集合)、一个邮箱(一组邮件的集合)。 Java中有很多种集…...
选择适合你的数据可视化工具:提升洞察力的关键决策
导言: 在当今数据驱动的世界中,数据可视化工具成为了帮助我们理解和传达数据见解的关键工具之一。数据可视化不仅能够将复杂的数据转化为易于理解的可视化形式,还能帮助我们发现数据中的模式、趋势和关联。然而,随着市场上可视化工…...

H5中的draggable
基本语法及事件 draggable 属性规定元素是否可拖动。必须设置,否则没有拖拽效果及事件触发 提示: 链接和图像默认是可拖动的。 提示: draggable 属性经常用于拖放操作 语法 <element draggable"true|false|auto"> 值描…...

搭建SVN服务器
简介 SVN(Subversion)是一种版本控制工具,用于管理和跟踪文件的修改历史。它可以帮助团队协作开发,方便地共享和更新代码,同时也可以提供备份和安全控制功能。 使用SVN,你可以创建中央代码库(…...

OpenCV之信用卡识别实战
文章目录 代码视频讲解模板匹配文件主程序(ocr_template_match.py)myutils.py 代码 链接: https://pan.baidu.com/s/1KjdiqkyYGfHk97wwgF-j3g?pwdhhkf 提取码: hhkf 视频讲解 链接: https://pan.baidu.com/s/1PZ6w5NcSOuKusBTNa3Ng2g?pwd79wr 提取码: 79wr 模板匹配文件 …...

Detector定位算法在FPGA中的实现——section1 原理推导
关于算法在FPGA中的实现,本次利用业余的时间推出一个系列章节,专门记录从算法的推导、Matlab的实现、FPGA的移植开发与仿真做一次完整的FPGA算法开发,在此做一下相关的记录和总结,做到温故知新。 这里以Detector在Global Coordinate System(原点为O)中运动为背景,Detect…...
心电信号去噪:方法与应用
目录 1 去噪技术的发展历程 2 滤波器去噪的应用 3 小波去噪的优势 4 深度学习去噪的前景...

睡眠助手/白噪音/助眠夜曲微信小程序源码下载 附教程
睡眠助手/白噪音/助眠夜曲微信小程序源码 附教程 支持分享海报 支持暗黑模式 包含了音频数据 最近很火的助眠小程序,前端vue,可以打包H5,APP,小程序 后台可以设置流量主广告,非常不错的源码 代码完整 完美运营 搭配无…...
Spring Cloud常见问题处理和代码分析
目录 1. 问题:如何在 Spring Cloud 中实现服务注册和发现?2. 问题:如何在 Spring Cloud 中实现分布式配置?3. 问题:如何在 Spring Cloud 中实现服务间的调用?4. 问题:如何在 Spring Cloud 中实现…...
debian怎么修改man help为中文,wsl怎么修改显示语言为中文
在Debian 12系统中,要将系统语言和Man帮助手册设置为中文,需要执行以下步骤: 安装中文语言包: 首先,更新软件包列表并安装中文语言包。打开终端并运行以下命令: sudo apt update sudo apt install locales配…...
k8s概念-亲和力与反亲和力
回到目录 亲和力 Affinity 对部署调度时的优先选择 分为 节点亲和力 pod亲和力 pod反亲和力 节点亲和力 NodeAffinity 进行 pod 调度时,优先调度到符合条件的亲和力节点上 可配置 硬亲和力和软亲和力 RequiredDuringSchedulingIgnoredDuringExecution 硬…...

【数据结构】实现单链表的增删查
目录 1.定义接口2.无头单链表实现接口2.1 头插addFirst2.2 尾插add2.3 删除元素remove2.4 修改元素set2.5 获取元素get 3.带头单链表实现接口3.1 头插addFirst3.2 尾插add3.3 删除元素remove3.4 判断是否包含元素element 1.定义接口 public interface SeqList<E>{//默认…...

Vue2 第二十节 vue-router (四)
1.全局前置路由和后置路由 2.独享路由守卫 3.组件内路由守卫 4.路由器的两种工作模式 路由 作用:对路由进行权限控制 分类:全局守卫,独享守卫,组件内守卫 一.全局前置路由和后置路由 ① 前置路由守卫:每次路由…...

第三章 图论 No.1单源最短路及其综合应用
文章目录 1129. 热浪1128. 信使1127. 香甜的黄油1126. 最小花费920. 最优乘车903. 昂贵的聘礼1135. 新年好340. 通信线路342. 道路与航线341. 最优贸易 做乘法的最短路时,若权值>0,只能用spfa来做,相等于加法中的负权边 1129. 热浪 1129.…...

❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件
❤ npm不是内部或外部命令,也不是可运行的程序 或批处理文件 cmd或者终端用nvm 安装提示: npm不是内部或外部命令,也不是可运行的程序或批处理文件 原因(一) 提示这个问题,有可能是Node没有安装,也有可能是没有配置…...

关于Godot游戏引擎制作流水灯
先上核心代码 游戏节点 流水灯的通途可以是 1. 装饰 2. 音乐类多媒体程序(如FL中TB-303的步进灯) FL Studio Transistor Bass...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...

DBLP数据库是什么?
DBLP(Digital Bibliography & Library Project)Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高,数据库文献更新速度很快,很好地反映了国际计算机科学学术研…...