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

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...