【数据结构】动态内存管理函数

news/2025/1/30 18:03:57/
cle class="baidu_pl">
cle_content" class="article_content clearfix">
content_views" class="markdown_views prism-atom-one-dark">cap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">

class="toc">

动态内存管理

  • 为什么存在动态内存管理
  • 动态内存函数的介绍
    • 🎊malloc
    • 补充:perror函数
    • 🎊free
    • 🎊calloc
    • 🎊realloc
  • 常见动态内存错误
    • 对空指针的解引用操作
    • 对动态开辟空间的越界访问
    • 对非动态开辟内存使用free释放
    • 使用free释放一块动态开辟内存的一部分
    • 对同一块动态内存多次释放
    • 动态开辟内存忘记释放(内存泄漏)

为什么存在动态内存管理

在此之前࿰c;我们开辟内存空间有两种方式。一种是创建一个已知类型的变量。
比如说:

<code class="prism language-c">class="token keyword">int aclass="token operator">=class="token number">10class="token punctuation">;  class="token comment">//在栈空间上开辟4个字节
code>

向系统申请了4个字节的内存空间。(对于 int型࿰c;4个字节它是固定的。)
还有一种是࿰c;创建一个数组。
比如说:

<code class="prism language-c">class="token keyword">int arrclass="token punctuation">[class="token number">10class="token punctuation">]class="token punctuation">; class="token comment">//在栈空间上开辟40个字节的连续空间。
code>

向系统申请了40个字节的内存空间。当这个数组开辟好了空间࿰c;没有办法改变它的大小。
对于数组的创建࿰c;它的内存开辟方式是比较死板的。

<code class="prism language-c">class="token keyword">int arr1class="token punctuation">[class="token number">10class="token punctuation">]class="token punctuation">;class="token keyword">int arr2class="token punctuation">[class="token number">100class="token punctuation">]code>

我们创建数组时࿰c;在一开始时就会指定数组的大小。arr1的内存空间为40个字节࿰c;可以存放10个整型元素。arr2的内存空间为400个字节࿰c;可以存放100个整型元素。

但有可能我们在使用数组arr1的时候࿰c;需要存放11个数组元素࿰c;而没有办法把它边长
我们可能为了尽可能满足很多情况࿰c;而创建一个很大的数组arr2࿰c;但在实际使用过程中࿰c;我们可能只会存放20个元素࿰c;而导致了内存空间的浪费

所以这样的内存开辟方式它是固定的࿰c;是不够灵活的。 不仅仅是上述的情况࿰c;有时候我们需要的空间大小在程序运行的时候才能知道࿰c;那么数组在编译时开辟空间的方式就不能满足了。 所以࿰c;我们需要学会开辟动态内存

动态内存函数的介绍

ckquote>

malloc
free
calloc
realloc

ckquote>

c_39">🎊malloc

malloc函数的原型:

<code class="prism language-c">class="token keyword">voidclass="token operator">* class="token function">mallocclass="token punctuation">(class="token class-name">size_t sizeclass="token punctuation">)class="token punctuation">;
code>

malloc声明在<code>stdlib.hcode>头文件中。
功能:
这个函数向内存申请一块连续可用的空间࿰c;并返回指向这块空间的指针

  • 如果开辟成功࿰c;则返回指向这块空间的指针。
  • 如果开辟失败࿰c;则返回一个NULL指针。
  • 返回值的类型是 void* ࿰c;所以malloc函数并不知道开辟空间的类型࿰c;具体在使用的时候使用者自己来决定。

因此࿰c;malloc函数的返回值一定要做检查。

举个例子:
原来࿰c;我们用<code>数组code>在<code>栈区code>开辟内存空间:
c="https://i-blog.csdnimg.cn/blog_migrate/546f7a3249f5e5c5988837a93113f737.png" alt="" />
现在࿰c;我们在<code>堆区code>动态开辟同样大小的内存空间:
c="https://i-blog.csdnimg.cn/blog_migrate/a9c071b60d643a5d1a023e17b2b29d26.png" alt="" />
根据malloc函数的原型࿰c;我们需要传递一个参数࿰c;以<code>字节code>为单位的内存。

<code class="prism language-c">class="token function">mallocclass="token punctuation">(class="token number">40class="token punctuation">)class="token comment">//即开辟了40个字节的内存空间
code>

然后࿰c;我们需要一个<code>指针pcode>来指向这块儿开辟好的<code>连续的code> <code>内存空间code>。
但由于 malloc函数的返回值为 <code>void*code>࿰c;即<code>无类型指针code>࿰c;所以我们需要先进行<code>强制转换code>࿰c;将无类型指针转换为<code>整型指针code>。
因此࿰c;

<code class="prism language-c">class="token keyword">intclass="token operator">* pclass="token operator">=class="token punctuation">(class="token keyword">intclass="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(class="token number">40class="token punctuation">)class="token punctuation">;
code>

此时我们࿰c;开辟的空间在内存中的堆区的空间࿰c;但是指向这块空间的指针是放在栈中的࿰c;也就是上面例子中的p指针。
如下图所示。
c="https://i-blog.csdnimg.cn/blog_migrate/51660384ac03349b4f4967b68c514424.png" alt="在这里插入图片描述" />

但是࿰c;正如我们上面所提到的࿰c;我们只是用malloc函数向<code>内存申请code>开辟40个自己的连续空间࿰c;不一定开辟成功。所以我们需要利用<code>指针pcode>进行进一步<code>检验code>。
c="https://i-blog.csdnimg.cn/blog_migrate/6558a7603a3c5cb04a883808b4a97f57.png" alt="" />
若开辟成功࿰c;进行访问:

c="https://i-blog.csdnimg.cn/blog_migrate/83f9b867a76bdd2bf1f0611559bc0657.png" alt="" />

malloc函数 申请的内存空间࿰c;但程序退出时࿰c;<code>不会主动释放code>的࿰c;需要使用<code>free函数code>来释放。
c="https://i-blog.csdnimg.cn/blog_migrate/12a82820511dea5e5c38adb4e7a08e2c.png" alt="" />

补充:perror函数

ckquote>

perror函数(忘的打印输出函数)
c="https://i-blog.csdnimg.cn/blog_migrate/a39bf1294be278a3de185f1664ab43d7.png" alt="" />
来自这篇博客:C语言perror函数详解
c="https://i-blog.csdnimg.cn/blog_migrate/f9ac0a7e56d133e5a354969cff74dc9d.png" alt="" />
c="https://i-blog.csdnimg.cn/blog_migrate/9b496809ea9fa849cc50fe507e79107d.png" alt="" />

ckquote> ckquote>ckquote>

🎊free

C语言提供free函数࿰c;专门用来做<code>动态内存code>的<code>释放和回收code>的。
free函数原型:

<code class="prism language-c">class="token keyword">void class="token function">freeclass="token punctuation">(class="token keyword">void class="token operator">*ptrclass="token punctuation">)class="token punctuation">;
code>

free函数也是声明在头文件<code><stdlib.h>code>中的。
free函数用来释放<code>动态开辟code>的内存。

  • 如果参数ptr指向的空间不是动态开辟的࿰c;那free函数的行为是未定义的。
  • 如果参数ptr是NULL指针࿰c;则函数什么事都不做。

calloc_117">🎊calloc

C语言还提供了一个函数叫callocc;calloc函数也用来动态内存分配。
calloc函数的原型:

<code class="prism language-c">class="token keyword">voidclass="token operator">* class="token function">callocclass="token punctuation">(class="token class-name">size_t numclass="token punctuation">,class="token class-name">size_t sizeclass="token punctuation">)class="token punctuation">;
code>

calloc函数的功能为࿰c;为<code>numcode>个大小为<code>sizecode>的元素开辟一块空间࿰c;并且把每个字节初始化为0。
与函数malloc函数的区别只在于calloc会在返回地址之前把申请的每个字节初始化为0。
即:

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">includeclass="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">includeclass="token string"><stdlib.h>
class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token keyword">intclass="token operator">* p class="token operator">= class="token punctuation">(class="token keyword">intclass="token operator">*class="token punctuation">)class="token function">callocclass="token punctuation">(class="token number">10class="token punctuation">, class="token keyword">sizeofclass="token punctuation">(class="token keyword">intclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token comment">//开辟10个大小为sizeof(int),即4个字节 的空间class="token comment">//判断是否开辟成功class="token keyword">if class="token punctuation">(p class="token operator">== class="token constant">NULLclass="token punctuation">)class="token punctuation">{class="token function">perrorclass="token punctuation">(class="token string">"calloc"class="token punctuation">)class="token punctuation">;class="token keyword">return class="token number">1class="token punctuation">;class="token punctuation">}class="token keyword">int i class="token operator">= class="token number">0class="token punctuation">;class="token keyword">for class="token punctuation">(i class="token operator">= class="token number">0class="token punctuation">; i class="token operator">< class="token number">10class="token punctuation">; iclass="token operator">++class="token punctuation">)class="token function">printfclass="token punctuation">(class="token string">"%d\n"class="token punctuation">, class="token operator">*class="token punctuation">(p class="token operator">+ iclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

c="https://i-blog.csdnimg.cn/blog_migrate/1a513197d89a0dfc5a2bf793d973dbdf.png" alt="在这里插入图片描述" />

c_152">🎊realloc

  • <code>realloc函数code>的出现让动态内存管理<code>更加灵活code>
    有时候我们发现之前申请的内存过小了࿰c;有时候我们发现我们申请的内存过大了࿰c;所以࿰c;在一些时候࿰c;我们需要对内存的大小做灵活的调整。那么<code>realloc函数code>就可以做到对动态开辟<code>内存大小的调整code>。
    realloc函数的原型如下:
<code class="prism language-c">class="token keyword">voidclass="token operator">* class="token function">reallocclass="token punctuation">(class="token keyword">voidclass="token operator">* ptrclass="token punctuation">,class="token class-name">size_t sizeclass="token punctuation">)class="token punctuation">;
code>

其中࿰c;ptr是要调整的<code>内存的地址code>。
size是调整之后的<code>新的code>内存的大小。
返回值是调整之后内存的起始位置。

这个函数调整原内存空间大小的基础上࿰c;还会将原来内存中的数据移动到<code>新code>的空间。
举个例子:
我们首先使用malloc函数开辟40个字节的空间:

<code class="prism language-c">class="token comment">//malloc函数申请空间
class="token keyword">intclass="token operator">* p class="token operator">= class="token punctuation">(class="token keyword">intclass="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(class="token number">40class="token punctuation">)class="token punctuation">;
class="token keyword">if class="token punctuation">(p class="token operator">== class="token constant">NULLclass="token punctuation">)
class="token punctuation">{class="token function">perrorclass="token punctuation">(class="token string">"malloc"class="token punctuation">)class="token punctuation">;class="token keyword">return class="token number">1class="token punctuation">;
class="token punctuation">}
class="token comment">//初始化
class="token keyword">int i class="token operator">= class="token number">0class="token punctuation">;
class="token keyword">for class="token punctuation">(i class="token operator">= class="token number">0class="token punctuation">; i class="token operator">< class="token number">10class="token punctuation">; iclass="token operator">++class="token punctuation">)class="token operator">*class="token punctuation">(p class="token operator">+ iclass="token punctuation">) class="token operator">= iclass="token punctuation">;
code>

我们想要将这个空间扩大࿰c;扩大为80个字节的空间。
于是我们使用realloc函数进行调整。但是内存空间的变化可能有不同的情况。
情况一࿰c;后面有足够的空间。
即这样的情形:
要扩展内存就直接在原有内存之后直接追加空间࿰c;原来空间的数据不发生变化。
c="https://i-blog.csdnimg.cn/blog_migrate/c3c3a5911e3f8e383c6c804aa1282272.png" alt="" />
情况二࿰c;后面没有足够的空间:
那么就在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
c="https://i-blog.csdnimg.cn/blog_migrate/086f99bffc7e4673cd4de8ae4ec6c34e.png" alt="" />

<code class="prism language-c">class="token comment">//增加一些空间
class="token keyword">int class="token operator">*ptrclass="token operator">=class="token function">reallocclass="token punctuation">(pclass="token punctuation">, class="token number">80class="token punctuation">)class="token punctuation">;
class="token keyword">if class="token punctuation">(ptr class="token operator">!= class="token constant">NULLclass="token punctuation">)
class="token punctuation">{p class="token operator">= ptrclass="token punctuation">;
class="token punctuation">}
code>

在用realloc函数调整动态内存空间时࿰c;要注意不能将原来的p指针࿰c;来接收realloc(p,80)。
这是因为relloc函数不一定开辟成功新的空间而进行调整࿰c;即realloc函数的返回值可能为NULL。
那么p=NULL࿰c;本来p维护40个字节的空间。那么这样那个40个字节空间的字节就没有指针维护了。但还没有释放࿰c;可能用不到了࿰c;但可能找不到了࿰c;从而造成内存泄露

常见动态内存错误

对空指针的解引用操作

当我们用malloc函数在堆上开辟了内存空间࿰c;此时会返回一个指针࿰c;如果࿰c;我们不判断返回值的话࿰c;可能就会发生对空指针解引用的错误。

比如:

<code class="prism language-c">class="token keyword">void class="token function">testclass="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token keyword">int class="token operator">*p class="token operator">= class="token punctuation">(class="token keyword">int class="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(INT_MAXclass="token operator">*class="token number">10class="token punctuation">)class="token punctuation">;class="token operator">*p class="token operator">= class="token number">20class="token punctuation">;class="token comment">//如果p的值是NULL࿰c;就会有问题class="token function">freeclass="token punctuation">(pclass="token punctuation">)class="token punctuation">;
class="token punctuation">}
code>
  • INT_MAX 是在计算机编程中表示有符号整型(signed integer)所能存储的最大值。

c="https://i-blog.csdnimg.cn/direct/db6e08ecf0db4bce84aa6d09cd5cfcd3.png" alt="" />
如上如࿰c;p是空指针。那么就发生了对空指针的解引用操作的错误。

对动态开辟空间的越界访问

这个道理和在栈上申请空间是一样的道理。
在堆上申请空间࿰c;超过范围越界访问就会报错。
c="https://i-blog.csdnimg.cn/direct/7bfa53573d2245e685760caf8c1929ff.png" alt="" />

<code class="prism language-c">class="token keyword">void class="token function">testclass="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token keyword">int i class="token operator">= class="token number">0class="token punctuation">;class="token keyword">int class="token operator">*p class="token operator">= class="token punctuation">(class="token keyword">int class="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(class="token number">10class="token operator">*class="token keyword">sizeofclass="token punctuation">(class="token keyword">intclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token keyword">ifclass="token punctuation">(class="token constant">NULL class="token operator">== pclass="token punctuation">)class="token punctuation">{class="token function">exitclass="token punctuation">(class="token operator">-class="token number">1class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token keyword">forclass="token punctuation">(iclass="token operator">=class="token number">0class="token punctuation">; iclass="token operator"><=class="token number">10class="token punctuation">; iclass="token operator">++class="token punctuation">)class="token punctuation">{class="token operator">*class="token punctuation">(pclass="token operator">+iclass="token punctuation">) class="token operator">= iclass="token punctuation">;class="token comment">//当i是10的时候越界访问class="token punctuation">}class="token function">freeclass="token punctuation">(pclass="token punctuation">)class="token punctuation">;
class="token punctuation">}
code>

对非动态开辟内存使用free释放

对于在栈上开辟的空间࿰c;却用free来释放…头脑不清醒可能会用free来释放吧…
c="https://i-blog.csdnimg.cn/direct/65f57db9ab9f4a178dddd4c518f318a0.png" alt="" />

使用free释放一块动态开辟内存的一部分

例如࿰c;下面的一段代码࿰c;p没有释放掉动态内存起始位置的那块空间。
这种错误就是在写代码的过程中࿰c;起始指针跑偏了࿰c;但自己可能没有意识到。
c="https://i-blog.csdnimg.cn/direct/7c6b44a37f224713ac1caa27013417a4.png" alt="" />
所以࿰c;一块连续的空间必须重头释放࿰c;一次性全部释放完。

对同一块动态内存多次释放

例如下面这段代码࿰c;它释放了两次࿰c;就会出现报错。
c="https://i-blog.csdnimg.cn/direct/9348f996be45441a8d9e6e70bb80b17f.png" alt="" />
更好的习惯是࿰c;当我们释放完一段空间后࿰c;将指针p设置为空指针。

<code class="prism language-c">class="token keyword">void class="token function">testclass="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token keyword">int class="token operator">*pclass="token operator">=class="token punctuation">(class="token keyword">int class="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(class="token number">100class="token punctuation">)class="token punctuation">;class="token function">freeclass="token punctuation">(pclass="token punctuation">)class="token punctuation">;pclass="token operator">=class="token constant">NULLclass="token punctuation">;class="token function">freeclass="token punctuation">(pclass="token punctuation">)class="token punctuation">;class="token comment">//此时就什么事就没有了
class="token punctuation">}
code>

动态开辟内存忘记释放(内存泄漏)

<code class="prism language-c">class="token keyword">void class="token function">test5class="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token keyword">intclass="token operator">* p class="token operator">= class="token punctuation">(class="token keyword">intclass="token operator">*class="token punctuation">)class="token function">mallocclass="token punctuation">(class="token number">100class="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(class="token constant">NULL class="token operator">!= pclass="token punctuation">)class="token punctuation">{class="token operator">*p class="token operator">= class="token number">20class="token punctuation">;class="token punctuation">}
class="token punctuation">}class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">)
class="token punctuation">{class="token function">test5class="token punctuation">(class="token punctuation">)class="token punctuation">;class="token keyword">whileclass="token punctuation">(class="token number">1class="token punctuation">)class="token punctuation">;class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

例如࿰c;这段代码࿰c;在主函数中࿰c;我调用test5()函数时࿰c;p指针在堆上申请了一块空间࿰c;但是函数调用完毕后࿰c;出了这个test5()函数࿰c;局部变量指针p已经销毁了。
但是࿰c;这在堆上开辟的100个空间还在占用。
并且出了这个函数࿰c;我们已经找不到这块空间的地址了࿰c;程序while(1)还在继续。
我们想用࿰c;但是不知道这块空间的起始地址࿰c;所以我们用不上。同样的࿰c;我们想要释放࿰c;我们还是释放不了。这就造成了内存泄漏
所以它只有当程序结束后࿰c;才会自动释放。


http://www.ppmy.cn/news/1567940.html

相关文章

16 分布式session和无状态的会话

在我们传统的应用中session存储在服务端&#xff0c;减少服务端的查询压力。如果以集群的方式部署&#xff0c;用户登录的session存储在该次登录的服务器节点上&#xff0c;如果下次访问服务端的请求落到其他节点上就需要重新生成session&#xff0c;这样用户需要频繁的登录。 …

双层Git管理项目,github托管显示正常

双层Git管理项目&#xff0c;github托管显示正常 背景 在写React项目时&#xff0c;使用Next.js,该项目默认由git托管。但是我有在项目代码外层记笔记的习惯&#xff0c;我就在外层使用了git托管。 目录如下 code 层内也有.git 文件&#xff0c;对其托管。 我没太在意&…

使用CSS快速居中div的七种方法

方法一&#xff1a;Flex布局 使用Flex布局是最简单的方法之一。只需在父容器中添加display: flex、justify-content: center和align-items: center即可实现水平和垂直居中。 .flex-container {display: flex;justify-content: center;align-items: center;height: 100vh;back…

<keep-alive> <component ></component> </keep-alive>缓存的组件实现组件,实现组件切换时每次都执行指定方法

<keep-alive> <component :is"currentRouter" :key"currentRouter"></component> </keep-alive>缓存的组件实现组件切换时子组件每次都执行指定的方法 // 父组件<keep-alive><component :is"currentRouter" :…

Zookeeper入门部署(单点与集群)

本篇文章基于docker方式部署zookeeper集群&#xff0c;请先安装docker 目录 1. docker初期准备 2.启动zookeeper 2.1 单点部署 2.2 集群部署 3. Linux脚本实现快速切换启动关闭 1. docker初期准备 拉取zookeeper镜像 docker pull zookeeper:3.5.6 如果拉取时间过长&#xf…

C#如何通过使用XpsToPdf库来转换xps为pdf文件

文章目录 英文描述中文描述 XpsToPdf库地址 调用方法&#xff1a; 英文描述 Output to PDF in WPF (for free!) There are two general strategies to outputting to a PDF in WPF. One is to output directly to a PDF which requires you traverse a visual or flow docum…

【四川乡镇界面】图层shp格式arcgis数据乡镇名称和编码2020年wgs84无偏移内容测评

本文将详细解析标题和描述中提到的IT知识点&#xff0c;主要涉及GIS&#xff08;Geographic Information System&#xff0c;地理信息系统&#xff09;技术&#xff0c;以及与之相关的文件格式和坐标系统。 我们要了解的是"shp"格式&#xff0c;这是一种广泛用于存储…

android studio生成jsk

JKS 文件&#xff08;Java KeyStore&#xff09;是 Android 开发中用于签名 APK 的密钥库文件。它包含用于签名的私钥和公钥&#xff0c;并保护其不被未授权使用。 在 Android 开发中&#xff0c;所有的 APK 文件在发布之前必须使用签名密钥进行签名&#xff1a; 调试签名&am…