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

devtools/2025/1/30 18:18:50/
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/devtools/154671.html

相关文章

关于产品和技术架构的思索

技术架构或者设计应该和产品设计分离&#xff0c;但是又不应该和产品架构独立。 听起来非常的绕并且难以理解。 下面我们用一个例子来解读这两者的关系 产品&#xff08;族谱图&#xff09; 如果把人类当作产品&#xff0c;那设计师应该是按照上面设计的(当然是正常的伦理道德)…

WPF基础 | 深入 WPF 事件机制:路由事件与自定义事件处理

WPF基础 | 深入 WPF 事件机制&#xff1a;路由事件与自定义事件处理 一、前言二、WPF 事件基础概念2.1 事件的定义与本质2.2 常见的 WPF 事件类型 三、路由事件3.1 路由事件的概念与原理3.2 路由事件的三个阶段3.3 路由事件的标识与注册3.4 常见的路由事件示例 四、自定义事件处…

docker desktop使用ollama在GPU上运行deepseek r1大模型

一、安装docker 安装WSL打开Hyper V 可以参考&#xff1a;用 Docker 快速安装软件_哔哩哔哩_bilibili 二、拉取ollama镜像 在powershell中运行如下命令&#xff0c;即可拉取最新版本的ollama镜像&#xff1a; docker pull ollama/ollama 如果需要指定版本&#xff0c;可以…

C# 添加、替换、提取、或删除Excel中的图片

在Excel中插入与数据相关的图片&#xff0c;能将关键数据或信息以更直观的方式呈现出来&#xff0c;使文档更加美观。此外&#xff0c;对于已有图片&#xff0c;你有事可能需要更新图片以确保信息的准确性&#xff0c;或者将Excel 中的图片单独保存&#xff0c;用于资料归档、备…

【starrocks学习】之catalog

目录 一、介绍 二、Catalog的分类 三、使用方法 四、简单示例 一、介绍 ‌StarRocks的Catalog功能‌是一种数据目录管理工具&#xff0c;用于同时管理和查询内部和外部数据。StarRocks从2.3版本开始支持Catalog功能&#xff0c;允许用户在一个系统中方便地访问和查询存储在…

状态模式——C++实现

目录 1. 状态模式简介 2. 代码示例 3. 单例状态对象 4. 状态模式与策略模式的辨析 1. 状态模式简介 状态模式是一种行为型模式。 状态模式的定义&#xff1a;状态模式允许对象在内部状态改变时改变它的行为&#xff0c;对象看起来好像修改了它的类。 通俗的说就是一个对象…

前端开发中的最新技术——CSS Container Queries: 自适应布局的新纪元

在前端开发领域&#xff0c;响应式设计早已成为必备技能。随着屏幕尺寸的多样化&#xff0c;我们需要不断优化页面布局以适应不同的设备和屏幕。现有的技术&#xff0c;如媒体查询&#xff08;Media Queries&#xff09;&#xff0c;虽然能够在不同的屏幕尺寸下提供基础的布局控…

【重生之我在学习C语言指针详解】

目录 ​编辑 --------------------------------------begin---------------------------------------- 引言 一、指针基础 1.1 内存地址 1.2 指针变量 1.3 指针声明 1.4 取地址运算符 & 1.5 解引用运算符 *** 二、指针运算 2.1 指针加减运算 2.2 指针关系运算 三…