C动态内存管理|有张三和如花的故事你心动了吗

news2025/2/22 16:51:07

C内存管理

  • C程序地址空间
  • 为什么存在?
    • 1.堆区空间足够大
    • 2.堆区空间大小更为灵活
  • 动态内存函数
    • malloc
    • free
      • free的注意事项
        • 内存泄漏
        • 没有free和free另外的细节
        • 不可对堆区的空间多次释放
        • 释放后要对指针置为NULL,避免野指针
        • free(NULL)会有影响吗
    • calloc
    • realloc
      • realloc重点--扩容问题
  • 避免踩坑
    • 重提野指针
    • 不可跳过起始位置去释放堆区的空间
  • 动态内存管理更深层次的理解
    • 内存实际申请的空间真的和我们申请的空间一样大吗?
    • `free()`到底释放了什么?
      • free:张三和如花的故事
  • 柔性数组
  • 结束语

在讲动态内存管理之前呢,我们需要对内存有一个更深层次的了解。一般在c语言中,称呼内存为c程序地址空间,因为内存很复杂,有很多不是c中的知识,称呼C程序地址空间更为贴切。

C程序地址空间

在这里插入图片描述

你怎么就知道向上、向下增长呢?来看图

在这里插入图片描述
在这里插入图片描述

如果你平时打印地址仔细观察的话,会发现在栈区上同一个变量的地址每次运行后都是不一样的。比如上面的那张图,我在打印一次,地址是不一样的。其它区的上变量的地址也可能改变,在修改动作过大的时候才会。
栈区上这个现象会很明显,那你知道为什么吗?

这种现象叫栈随机化,意思就是说变量的地址是随机的,不会固定的,这是编译器对变量的一种保护机制,如果知道了变量的地址,是可以对变量进行篡改的,如果一个黑客,知道变量的地址,就可以根据这个地址去篡改一些东西,这是非常危险的。

在这里插入图片描述

为什么存在?

1.堆区空间足够大

技术层面上,普通的空间申请都是在栈区和全局,全局区尽量少用。但是栈区的空间有限,如果需要很大空间的话,只能在堆区。

在这里插入图片描述

在堆区上申请,完全是可以的。

在这里插入图片描述

2.堆区空间大小更为灵活

数组中大小必须固定了(不考虑变长数组),如果在使用过程中空间不够了,只能回到定义数组的地方去换一个更大的空间,而使用堆区,它的空间大小是可以随时更改的这是和realloc函数有关,这就对顺序表就很有帮助了。

顺序表不会用数组的,用起来太挫了,因为空间的限制,开辟大了又浪费,开辟小了又不够。管理起来是很不方便的。
通过动态内存函数在堆区申请的空间,这些空间的地址都是连续的,并且在使用过程中是可以扩容或者缩小的,这是数组不具备的。因为空间地址是连续的,因此是可以在堆区上去实现顺序表。
具体是如何实现顺序表,以及如何扩容的,可以去看看这篇博客通讯录的实现,这里不仅包括了顺序表的实现和扩容,还包括了本章另一个重点柔性数组

动态内存函数

头文件

#include<stdlib.h>

malloc

在这里插入图片描述

malloc函数功能:向堆区申请size个字节大小的空间。
返回值:是这块空间的起始地址,因为不知道具体的类型所以返回的是void*,具体情况根据使用者来定,潜台词是需要强制类型转换。如果开辟失败了,返回一个NULL

这里可以规范一下代码风格,可以参照我写的,也可以不用。
大小:单个元素的空间大小*元素的个数
这样逻辑会比较清晰,另外if语句进行的条件判断,最好写,不要怕麻烦,能够提醒自己这里会存在开辟空间失败的情况,免得以后在这里吃亏。

在这里插入图片描述

可以看看开辟失败的情况:

在这里插入图片描述

可以去验证一下,开辟的空间是不是连续的。
每个地址都是相差一个4个字节,因为一个int类型的元素占4个字节的空间大小,可以换成char类型的去打印,那结果一定是连续的。

在这里插入图片描述

free

C程序地址空间那块有说到有借有还再借不难,不还存在风险,在生活中嘛,不还钱风险挺大的,在C中不还空间,会造成一个很大的问题-----内存泄漏。等下可以给大家演示一下。

在这里插入图片描述

free函数功能:释放堆区开辟的空间。
只能传堆区空间的起始地址

free的注意事项

内存泄漏

堆区申请的空间,必须释放,否则会发生内存泄漏。
内存泄漏:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

可以看到一直在吃内存,但是吃到一定程度上,不会在消耗内存了,这是因为存在一些保护机制。
这是一个很直观的方法去看内存泄漏,对现在的你来说可能没什么影响,但是如果你是一名程序猿,在工作中写了个类似这样的程序,那会有很大的问题的,比如电脑上的杀毒软件,你说它里面能存在内存泄漏嘛?又或是操作系统,服务器程序,这些程序,你说它能有内存泄漏吗?是不能存在的。

一个疑问,程序关闭后,内存泄漏对电脑有影响吗?答案是没有影响,如果你安装的程序存在内存泄漏,你每次只要关机重启,电脑又可以使用了,但过一会又会死机,内存泄漏是根本原因。

没有free和free另外的细节

在学了动态内存函数之后,如果你学到了数据结构,比如说顺序表,存在越界的情况,有free和无free,编译器检测的效果是不一样的。
越界情况是在free过程中去检测的,free本身的报错是地址传错,下面图片展示一下。

释放非堆区的地址空间:

在这里插入图片描述

越界情况:

free,有很明显的越界行为,但是没有报错。

在这里插入图片描述

那有free了呢

在这里插入图片描述

这是我单独拿出来讲,很容易知道是这里出错了,但是真到了自己写代码的时候,如果是数组,越界是不会报错的,如果使用动态内存管理,越界+free报错后,你可能都不知道是哪错了,就比如在实现顺序表的增删查改等功能的时候。

不可对堆区的空间多次释放

在这里插入图片描述

写在同一个文件中,犯这中错误的概率很低,但是如果是多文件中呢?
这里可以举一个通讯录中会写出多次释放的一个例子,写着写着,可能自己都不会记得,在哪释放过了。
通讯录

在这里插入图片描述

释放后要对指针置为NULL,避免野指针

比如free(p),p指针仍然指向那块堆区的空间,你不置为NULL,如果在free(p)下面还有代码,再次对p进行解引那是错误的,因此要置为NULL

free(NULL)会有影响吗

并不会有任何影响。

在这里插入图片描述

calloc

在这里插入图片描述

calloc函数功能:向堆区开辟空间,并作初始化。
size_t num:元素的个数
size_t size:元素占空间大小
返回值:同样是开辟空间的起始地址,需要根据实际情况去强制类型转换,开辟失败同样会返回一个NULL

和这种写法表达的意思一样

在这里插入图片描述

同样写的时候注意规范,也要加上条件判断,时刻提醒自己存在开辟失败的情况。
观察初始化:

在这里插入图片描述

realloc

前三个还好,这个函数很重要,也存在很多细节,也可以举在通讯录中的例子。

在这里插入图片描述

realloc函数功能:对已经存在堆区的空间进行调整,可以扩容,也可以缩小,或者是开辟空间。
void* ptr: 存在两种形式:①NULL ②已经存在堆区的起始地址,对于①这种去调用realloc,此时realloc功能和malloc一样,根据size去开辟空间。(这个地址是任意类型的,只要在堆区就行)
size_t size:原有空间大小+调整的空间大小,比如说原来已经有4个字节的空间,现在扩容2个字节的空间,那么size6个字节的空间
返回值:也是堆区空间的起始地址,可能和ptr原指向的地址空间不一样,待会会详细说,正是因为这个,所以有坑。如果开辟空间失败也会返回NULL

在这里插入图片描述

①参数为NULL,功能和malloc一样

在这里插入图片描述

realloc重点–扩容问题

原有空间后面不存在足够空间的情况:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这种现象是随机的,在调用这个函数的时候要特别注意。
再拿通讯录中的例子说明。

在写增加联系人的时候,需要判断容量是否满了,满了需要进行扩容,在这里设计的时候,如果将扩容封装成函数,如果是用柔性数组设计的通讯录,这个扩容函数,要么带返回值,返回这个新的地址,要么是在传参的时候传一级指针的地址,用二级指针去接收。

另一种形式设计的通讯录就不用这样设计,主要原因是函数调用的问题,这里不在过多描述,更详细的要去博客中了解。
只记住realloc可能会更改地址,在传参的时候要注意。

部分截图
在这里插入图片描述

原有空间后面存在足够大的空间的情况:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在用realloc时,代码风格同样也要规范。
刚刚就说过了,如果realloc开辟失败,会返回NULL。可以想一下,如果开辟失败,指针c去接收了这个NULL,那原来堆区的空间不就找不到了吗?因此需要一个新的指针去接收返回值,再去判断开辟释放成功,开辟成功则交给指针指针c去维护。最好是用同一个指针c去维护堆区的空间,指针p只是一个中转站。

在这里插入图片描述

避免踩坑

除了free那提到的一些注意事项,还有下面的。

重提野指针

在详解数组和指针的那些知识中有详细介绍,上面也略微提到了,这里还是需要强调一下。
使用动态内存函数是必须和指针打交道的,对于指针的规范使用是很有必要的。
在对指针初始化的时候,如果指针没有明确指向,需要置为NULL

不可跳过起始位置去释放堆区的空间

free释放的必须是起始位置,不能只释放其中一部分。什么时候会犯这种错误呢?在对指针进行自增操作的时候。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

也需要去规范,一般都是写成数组的形式去遍历堆区的空间中的元素

在这里插入图片描述

动态内存管理更深层次的理解

内存实际申请的空间真的和我们申请的空间一样大吗?

释放之前

在这里插入图片描述

释放之后,观察内存发现释放的空间比我们开辟的空间要多,换句话说,在开辟空间的时候,实际上内存申请的空间比我们申请的空间大,程序猿使用的是程序猿申请的空间,那些多出来了的空间是用来干嘛的呢?

在这里插入图片描述

多出来的空间是用来记录一些信息,记录什么信息呢?比如实际申请出来多大的空间,还有其它属性的信息。记录实际开辟空间大小,在free的时候就可以根据这个地址去释放多大的空间,一般实际开辟的空间和编译器有关。

free()到底释放了什么?

free:张三和如花的故事

释放后,指针仍然指向堆区的空间,但是不能访问。free究竟释放了什么?释放的本质是解除指针和对应堆空间的关系,虽然指针保留了这个地址,但是现在没有权力去访问它了。
举一个很生动的例子:如花和张三分手了,但是张三很痴情,仍然记得如花,但这个时候张三还能和如花手拉手、每天打电话吗?不能了,如花会说骚扰,然后…这应该很形象了吧φ(゜▽゜*)♪

柔性数组

柔性数组和结构体有关。

结构中的柔性数组成员前面必须至少一个其他成员。柔性数组数组存在下面两种形式有0和无0的

struct S
{
	int a;
	char data[];
    //char data[0]; 
};

sizeof 返回结构体的大小不包括柔性数组的内存
不记得的可以看看结构体的大小

在这里插入图片描述

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大
,以适应柔性数组的预期大小。

一般先给结构体分配好空间,再给柔性数组分配空间
以通讯录中的为例
伪代码如下:

typedef struct PeopleInfo
{
	char name[20];
	int age;
	char tele[12];
	char address[30];
}People;

typedef struct Contacts
  {
	int count;//当前
	People data[1000];
  }Contacts;
  
Contacts* Con = (Contacts*)malloc(sizeof(Contacts) + sizeof(People) * X);

分配空间后是下面这个样子的,通过数组名data和下标索引就可以对柔性数组进行操作了。

在这里插入图片描述

结束语

把结构体和动态内存管理都掌握了,可以去实现小项目通讯录,多写才能熟能生巧,才能深刻理解。那篇博客写的比较全,单链表、顺序表的和柔性数组的形式都有,可以参考下。

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

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

相关文章

报表工具怎么选?JAVA开源工具那么好用,为什么大家还花钱买商用

做报表很长时间了&#xff0c;最近发现一个比较奇怪的现象&#xff1a;各家工具使出各种手段做广告、吸引注意力&#xff0c;但是受到程序员热烈追捧的反倒一直是 Jaspereport &#xff0b; ireport 这种免费、开源的 JAVA 工具&#xff0c;几个开了专版讨论 JAVA 报表的论坛里…

场景应用:你知道 i = i++;的含义么?

文章目录引言正文题目原理i i;呢&#xff1f;总结引言 今年面试官小姐姐问了一个灵魂问题&#xff1a;i0; i i;等于多少&#xff1f; 当时人就傻了 当然&#xff0c;面试官小姐姐还是很可爱的&#xff0c;人也很好&#xff0c;让我研究一下&#xff0c;好&#xff0c;那么…

毕业设计 基于CNN实现谣言检测 - python 深度学习 机器学习

文章目录1 前言1.1 背景2 数据集3 实现过程4 CNN网络实现5 模型训练部分6 模型评估7 预测结果8 最后1 前言 Hi&#xff0c;大家好&#xff0c;这里是丹成学长&#xff0c;今天向大家介绍 一个深度学习项目 基于CNN实现谣言检测 1.1 背景 社交媒体的发展在加速信息传播的同时…

多层高速PCB设计学习(一)初探基本知识(附单层设计补充)

目录前言一、常见概念名词科普二、层数的选择三、基本原则二、层叠结构分析电源层和地层耦合各层的种类选择三、元器件布局及布线单层知识点补充前言 简单学会两层板的设计方法&#xff0c;想学习四层板以及多层板的设计方法&#xff0c;立创EDA上有开源的四层板的四旋翼飞机的…

(4)UART应用设计及仿真验证(整体回顾)

在新公司入职以后,第一个小demo就是设计一个UART模块,支持apb2.0,支持中断上报,支持环回,支持有效数据位可配置,支持校验可配置,支持FIFO水位线可配置,支持波特率可配置等等。UART最早是在补习班的时候接触的,当时学习地很吃力,对它地理解不算深刻。当时实现的只有发…

Future、FutureTask类解析

Future类 Future类提供了方法来检查异步调用是否完成、等待异步调用完成并获取异步调用返回结果。get()方法可以对线程进行阻塞&#xff0c;直到异步调用完成并返回结果。cancel()方法可以取消异步方法的执行。 Future是一个接口&#xff0c;定义了异步线程的返回结果的获取方法…

初学python非常实用的10个小技巧,先收藏再说~

嗨害大家好鸭&#xff01;我是小熊猫❤ 最近双十一是不是都在买买买呢&#xff1f; 但是学习这件事情可不能懈怠鸭&#xff01; 今天就来讲讲python实用小技巧~ 源码、资料电子书点击此处 1.唯一性 以下方法可以检查给定列表是否有重复的地方&#xff0c; 可用set&#xff…

力扣刷题day37|1049最后一块石头的重量 II、494目标和、474一和零

文章目录1049. 最后一块石头的重量 II思路动态规划五部曲494. 目标和回溯思路动态规划背包思路动态规划五部曲474. 一和零思路动态规划五部曲1049. 最后一块石头的重量 II 力扣题目链接 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量…

深度学习模型部署全流程-模型训练

文章目录前言模型训练全流程1.数据准备2.数据加载3.搭建神经网络4.设置损失函数&#xff0c;优化器5.训练网络模型6.模型测试7.完整代码9.训练结果小结前言 该系列文章会介绍神经网络模型从训练到部署的全流程&#xff0c;对于已经参加工作的人可以快速的了解如何使用深度学习…

Android Studio入门之文本内容、大小、颜色的讲解及实战(附源码 超详细必看)

运行有问题或需要源码请点赞关注收藏后评论区留言或私信博主 一、设置文本的内容 1:在XML文件中通过属性android:text设置文本 <TextViewandroid:layout_width"wrap_content"android:layout_height"wrap_content"android:text"Hello World!"…

nordic 52832中添加RTT打印

JlinkRTT RTT是基于Jlink调试器的实时传输技术,可以代替串口打印一些调试信息,不需要额外接线。 nordic 52832官方例程中,会将RTT打印函数做进一步封装,下面就讲一下怎么开启52832中的RTT打印。 第一步 增加RTT代码 RTT源代码可以在segger官方网站下载,也可以在nordic 5…

使用 stream buffer 传递数据

使用 stream buffer 传递数据 概述 如前所述&#xff0c;队列虽然提供了任务之间传递数据的功能&#xff0c;但没有对通知机制进行优化&#xff0c;即不方便实现多次采集不同长度的数据&#xff0c;然后触发一次通知接收的机制。 特性概述 Streambuffer 的中文含意是“流式…

Chapter5.5:频率响应法

此系列属于胡寿松《自动控制原理题海与考研指导》(第三版)习题精选&#xff0c;仅包含部分经典习题&#xff0c;需要完整版习题答案请自行查找&#xff0c;本系列属于知识点巩固部分&#xff0c;搭配如下几个系列进行学习&#xff0c;可用于期末考试和考研复习。 自动控制原理(…

Hive与Hbase的区别与联系

一、概念 1&#xff0c;Hive hive是基于Hadoop的一个数据仓库工具&#xff0c;用来进行数据提取、转化、加载&#xff0c;这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。hive数据仓库工具能将结构化的数据文件映射为一张数据库表&#xff0c;并提供SQL查询…

网站中的经典,分享那些我用过的宝藏网站

前言 本篇将会具体分享我在最开始学习编程时了解到的网站&#xff0c;并分享自己使用这些网站的感受&#xff0c;当然&#xff0c;如果我有说的不正确的或者需要补充的&#xff0c;欢迎评论区补充纠正。还有各位来自优秀学校的伙伴们&#xff0c;或许其中一些资源在你们的学校…

安卓开发Android studio学习笔记15:关于如何使用Okhttp框架的网络请求(调用API接口)

Android studio一、安卓基于HTTP网络编程(一)、两种请求方式(二&#xff09;、安卓基于HTTP网络编程的两种方式1、使用HttpURLConnection访问网络资源**2、利用HttpClient访问网络资源**&#xff08;1&#xff09;HttpGet&#xff08;2&#xff09;HttpPost二、基础Okhttp的网络…

修改 echarts 默认样式记录

1、修改折线图上的数据标记点 showSymbol:false , 表示不展示数据点&#xff0c;只有鼠标 hover 时&#xff0c; tooltip 展示。 series: [{name: 进场, // 名称&#xff0c;图例和 tooltip 中展示showSymbol: false, // 不展示数据标记点type: line, // 类型color: #0091FF…

大学解惑10 - CSS中的content怎么换行,以及使用before伪类的优点

大学解惑09 - 单独用HTML javascript CSS 实现三版99乘法表&#xff0c;你就是班里最靓的仔https://blog.csdn.net/xingyu_qie/article/details/127631612 ☆ 上一篇文章用前端HTML CSS JS基础写了3版99乘法表&#xff0c;有同学说终于把99乘法表写透了&#xff0c;但是紧接着就…

Linux关于JDK、Tomcat以及MySQL安装

目录 一、JDK安装 1、 上传jdk、tomcat安装包 2、解压两个工具包 3、配置环境 4、在配置文件中加入java环境变量&#xff1a; 5、保存&#xff0c;让新设置的环境变量生效 二、Tomcat安装 1、将tomcat解压到/opt下 2、配置环境变量 3、启动tomcat 4、创建启动脚本 三…

入门学习XSS漏洞,这一篇就够了

入门学习XSS漏洞&#xff0c;这一篇就够了1.XSS简介2.XSS的类型反射型XSS存储型XSSDOM型XSS1.XSS简介 XSS攻击&#xff0c;通常指黑客通过“HTML注入”篡改了网页&#xff0c;插入了恶意的脚本&#xff0c;从而在用户浏览网页时&#xff0c;控制用户浏览器的一种攻击。在一开始…