目录
1.前言
在我刚接触动态内存分配的时候,我总是觉得奇怪,例如我们要存如一串数字,为什么要用malloc进行内存分配?不应该直接用int定义一个数组更快更简单吗?动态内存分配没有任何意义啊!(我当时的真实想法)。如果你也有相同或类似的疑惑,那相信你看完文章一定会大受启发!没有这种疑惑也给我看!
2.栈与堆
1.死板的老头---栈
要引入动态内存,我们先从老朋友栈说起。一个程序开始执行的时候,系统会预先给栈留下一块地盘,这块地盘的大小是固定的,栈会死守这块宝贵的地盘,所以当程序运行期间,我们不能更改栈的大小,并且如果我们对栈的使用超出了系统预留给他的大小,例如,如果有粗心的小朋友写了一个错误的无限递归函数,栈便会崩溃,发生栈溢出(stack over flow)。(咱可以想象向一个固定大小的杯子倒大于容量的水或无限倒水会发生什么)
总的来说,咱们死板的栈有两大缺点:一是如果你的变量是在栈上分配,那我们便不能实时操控变量范围。(之前的文章有提到)。二是如果我们需要定义一个较大的数据类型,如大数组,并想在运行期间根据参数决定数组大小, 那么栈是做不到的。
2.灵活的胖子---堆
与栈这个顽固分子相反,我们的堆更加灵活且大方(物理上的大方),堆(heap),又叫内存空闲地、动态内存(dynamic memory), 没有固定的大小,他的大小在程序存在期间都是可以灵活变动的,我们可以随意控制在堆上分配多少内存以及我们要将内存数据保留到什么时候再清除(当然也不能超过系统限制,别太放肆!),而栈是用完就扫走并丢掉用过的内存数据。
(注意这里的堆和数据结构里的堆结构没有任何关系,他们不是一个同东西)
为了对这片新发现的动态内存小岛进行操作,我们将认识以下四位新角色!
3. malloc、calloc、realloc、free
malloc(马虎の冒险家)
函数原型: void* malloc(size_t size);
其中参数size_t size表示动态内存分配空间的大小,以字节为单位。
当我们想在堆这座未知小岛上获取一点内存时,我们便派出malloc去小岛上探险,让他寻找一块可用的内存地,并返回一个指向这块内存起始地的void类型指针(所以我们要进行一个强制转换)。这里的size_t意味着我们给他的参数需要是0或正数(类似unsigned)。下面我们看看操作实例:
#include<stdio.h>
#include<stdlib.h>//(1)
int main() {
int* p;
p = (int*)malloc(sizeof(int));//(2)
*p = 10;//(3)
}
需要注意的有以下三点:
(1):要使用我们四位新角色,都需要带有这个头文件。
(2):因为malloc函数返回的是一个空指针,所以我们需要进行一个强制转换--->(int*)。
(3):通过解引用的方式赋值。
我们通过这张图来理解一下malloc是如何执行他的任务的:
当程序开始运行时,我们的栈会先为主函数开辟一个栈帧储存局部变量,此时我们创建了一个指针p。然后我们命令malloc函数说:“现在你去小岛上给我找一块能用的空地出来。”于是malloc这个冒险家便在小岛上四处寻找,发现了地址200—204这一块是可以使用的,便通过void类型指针返回给我们的int类型指针p这块地的起始地址200。(需要进行强制转换)
接着我们通过解引用指针p,便可以将这片空地上的值改为10啦!
那么问题来了,为什么我们把malloc称为马虎的冒险家呢?
原因是malloc并不会将找到的那块地打扫干净,里面都是一堆垃圾值。让我们改写程序来看一下。
#include<stdio.h>
#include<stdlib.h>
int main() {
int* p;
p = (int*)malloc(sizeof(int));
printf("%d", *p);
}
若我们不对p进行解引用赋值,然后我们来看看malloc为我们找到的地盘上有什么:
-842150451
总结一下:我们用栈上的p存了堆上内存的地址 ,用解引用赋值。使用堆上内存的唯一方式就是引用,malloc做的事情只是找到一个堆上的空地,为你留下,然后通过指针返回给你。而你去访问的方式就是通过自己建立一个指针。如果malloc找不到,就会返回NULL。并且我们不能解引用一个void类型的指针。
当然,有马虎的冒险家,就有细心的冒险家,接下来我们来看看calloc!
calloc(细心の冒险家)
函数原型:void* calloc (size_t num, size_t size);
功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
我们来用他写一段代码:
#include<stdio.h>
#include<stdlib.h>
int main() {
int* p;
p = (int*)calloc(1,sizeof(int));
printf("%d", *p);
}
让我们来运行一下:
0
可以看到,虽然我们并没有为calloc先生找到的空地赋值,但我们输出*p时,我们却得到了0!!这是因为calloc有洁癖,当他找到一篇空地时,他会把这片空地打扫干净(初始化为0)。然后将这片地的初始地址通过空指针返回给我们。除了这一点外,calloc和malloc所做的大致都是一样的。
realloc(勤劳且万能の工具人)
函数原型:void *realloc(void *ptr, size_t size)ptr:指向已分配内存起始地址的指针
size_t size:新内存块的大小
如果你对malloc和calloc的寻找的空地不满意,并且想修改这片空地的大小(变大或变小都行),我们便可以叫出我们的勤劳的工具人realloc,我们先讲扩大的方式,realloc有两种方式扩大我们的地盘,扩建或者搬家。
扩建:
如果我们的旧地盘旁边没有其他被占用的内存,那么realloc的扩建方式就是这样:
搬家:
当我们的旧地盘旁边环境恶劣已经无法使用时(已经存放其他东西):
那么realloc的扩建方式就是这样:
他会为我们在新的地方创建一块我们期望大小的地盘,并把我们之前存放的东西搬过去,再释放之前的内存!
当然!我们的realloc不仅勤劳,他被称为万能工具人也不是没有道理的!为什么万能呢?因为他可以当作malloc和free来使用!
当malloc使用:
我们只需要把第一个参数改为NULL即可!(例如:void *realloc(NULL, sizeof(int))
当free使用:
当free的原理是如果我们用realloc来压缩内存块的大小,那么被压缩掉的地方会被释放。因此我们只需要将期望的内存块大小改成0。那么整个内存块就会被全部释放!
(例如:void *realloc(A, 0))
free(重要の清洁工人)
函数原型:void free(void *ptr)指针指向内存的起始地址
释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。
free在动态内存的使用中扮演了非常重要的角色,当我们在堆上申请了一块内存后,如果我们不显式地使用free去释放它,那么这块内存就会一直被占用,如果我们多次使用程序在堆上创建内存并且不去使用free释放掉它们,那么未被使用和引用的内存将一直占据在堆上,成为垃圾内存。然后垃圾内存将急剧增加,造成内存泄漏!
所以千万千万记得使用free!
3.作者碎碎念
这是第二篇精心制作的博客,我每周都会分享一篇我记录认真学习后产生的感悟的博客,希望我能用这种方法督促自己在学习时候有更多的思考,并且持续产出高质量的内容分享给大家,如果有任何错误,也欢迎大家指出,让我们共同进步!