content_views"
class="markdown_views prism-atom-one-dark">
为什么存在动态内存管理
在此之前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;而创建一个很大的数组arr2c;但在实际使用过程中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=NULLc;本来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的值是NULLc;就会有问题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;才会自动释放。