学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...

C语言 函数指针详解
一、函数指针 1.1、概念 函数指针:首先它是一个指针,一个指向函数的指针,在内存空间中存放的是函数的地址; 示例: int Add(int x,int y) {return xy;} int main() {printf("%p\n",&Add);…...

LNMP及论坛搭建
安装 Nginx 服务 systemctl stop firewalld systemctl disable firewalld setenforce 0 1.安装依赖包 #nginx的配置及运行需要pcre、zlib等软件包的支持,因此需要安装这些软件的开发包,以便提供相应的库和头文件。 yum -y install pcre-devel zlib-devel…...

【使用机器学习和深度学习对城市声音进行分类】基于两种技术(ML和DL)对音频数据(城市声音)进行分类(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

Godot 4 练习 - 制作粒子
演示项目dodge_the_creeps中,有一个Trail,具体运行效果 想要看看咋实现的,看完也不清晰,感觉是要设置某些关键的属性 ChatGPT说:以下是一些重要的属性: texture:用于渲染粒子的纹理。您可以使用…...

Java基础继承详解
Java基础继承详解 在Java中,继承是面向对象编程中的一个重要概念。通过继承,一个类可以从另一个类继承属性和方法,使代码重用和扩展更加方便。下面是关于Java基础继承的一些详解: 关键字: 使用extends关键字可以在一个…...

如何维护你的电脑:打造IT人的重要武器
文章目录 方向一:介绍我的电脑方向二:介绍我的日常维护措施1. 定期清理和优化2. 保持良好的上网习惯和安全防护3. 合理安排软件和硬件的使用4. 数据备份和系统还原 方向三:推荐的维护技巧1. 数据分区和多系统安装2. 内部清洁和散热优化3. 安全…...

【雕爷学编程】MicroPython动手做(31)——物联网之Easy IoT 3
1、物联网的诞生 美国计算机巨头微软(Microsoft)创办人、世界首富比尔盖茨,在1995年出版的《未来之路》一书中,提及“物物互联”。1998年麻省理工学院提出,当时被称作EPC系统的物联网构想。2005年11月,国际电信联盟发布《ITU互联网…...

Elasticsearch 快照和恢复
文章目录 简介快照存储库说明创建或更新存储库接口说明路径参数查询参数请求正文 使用 fs 方式创建存储库验证储存库获取存储库信息删除存储库清理储存库 快照创建快照路径参数查询参数请求正文示例 获取快照查询参数示例 克隆快照查询参数示例 获取快照状态示例 恢复快照查询参…...

Packet Tracer - 检验 IPv4 和 IPv6 编址
Packet Tracer - 检验 IPv4 和 IPv6 编址 地址分配表 设备 接口 IPv4 地址 子网掩码 默认网关 IPv6 地址/前缀 R1 G0/0 10.10.1.97 255.255.255.224 N/A 2001:DB8:1:1::1/64 N/A S0/0/1 10.10.1.6 255.255.255.252 N/A 2001:DB8:1:2::2/64 N/A 本地链路 F…...

PHP8的表达式-PHP8知识详解
表达式是 PHP 最重要的基石。在 PHP8中,几乎所写的任何东西都是一个表达式。简单但却最精确的定义一个表达式的方式就是"任何有值的东西"。 最基本的表达式形式是常量和变量。当键入"$a 5",即将值"5"分配给变量 $a。&quo…...