目录
为什么要进行动态内存开辟?
malloc和free
malloc函数
free函数
malloc函数和free函数的实际应用
calloc函数
realloc函数
realloc函数的使用示例
大家好,本次博客的主要内容是与内存分配相关的函数详解。在学习这部分内容之前我们先来普及一个小知识,以便于我们更好地理解为什么要采用动态内存分配。
首先我想要告诉大家的是,在我们的计算机上不同的数据存储的位置不同。存储数据的区域大致分为三个部分:
第一部分 栈区:主要存储局部变量和函数形参。
由于我们的局部变量大多具有特定的生命周期属于临时存储,一旦出了变量的作用域就会被销毁,(主函数除外,在主函数中创建的局部变量到程序结束的时候才会被销毁。)具有很高的再利用的特点,所以栈区在内存中所占的空间不大。
第一部分 堆区:主要进行动态内存开辟。(是我们本次主要讲的对象。)
第一部分 静态区(也叫数据段):主要用于存储全局变量以及静态变量。
存储在静态区中的数据只有当程序结束的时候才会被销毁。
有了上面的铺垫那么我们就可以开始我们今天内存分配函数相关知识的讲解了。
动态内存开辟也就是在堆区上面开辟一块空间供我们利用,但是很多小伙伴在刚开始的时候并不明白为什么要在堆区里开辟一块空间,明明我们直接创建临时变量的时候就可以直接进行使用了呀?
还记得我们上面提到的局部变量的存储位置是在栈区上吗?因为栈区中的内容可以反复利用(出作用域局部变量就被销毁)所以我们栈区的大小并不大。但是在主函数中创建的临时变量的生命周期又是整个程序的运作周期,那么假设我们想要在主函数中创建一个很大的数组,是否有可能将我们栈区的内容完全占据呢?没错,在堆区上进行动态内存开辟就是为了避免这种情况的发生。是不是理解了不少呢?我们接下来就进入动态内存分配相关知识的正式讲解。
对于一个未知的函数我们第一步要做的就是先了解这个函数参数以及返回类型是什么,因此我们先通过两张张图片初步认识一下malloc函数和free函数的参数与返回类型,之后再对这两个函数进行进一步的解说。
我们通过上面的图片可以看到 malloc 函数的参数为一个无符号整数,返回类型为 void * 类型。其中作为参数的无符号整数是我们想要在堆区中开辟的字节的个数。因为 malloc 函数的返回类型是一个指针类型所以我们接收 malloc 函数的返回值的时候需要用一个指针变量进行接收。
malloc 函数会向堆区申请开辟一块连续可用的空间。 如果开辟成功,则返回一个指向开辟好空间的指针(该空间的首地址,可以类比于数组的首地址)。 如果开辟失败,则返回一个NULL指针(堆区中已经没有符合条件的连续的空间以供开辟使用)。因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。但是需要我们注意的是:对于 void * 类型的指针我们在使用的时候不能直接进行解引用得到我们想要的值,因此我们在接收 malloc 函数的返回值的时候需要进行强转操作,将 void * 转换成我们需要的相关类型的指针,以便我们更好的使用。
相比较于 malloc 函数来说我们的 free 函数就简单得多,但是其重要程度并不亚于任何动态内存开辟函数。经过上图我们可以知道的是 free 函数的参数为一个任意类型指针(实际上就是我们在上一步骤用来接收开辟动态内存的指针。)无返回类型因此我们并不需要注意。
free函数是专门是用来做动态内存的释放和回收的,当我们在堆区中开辟一块空间之后系统并不会在我们使用完主动将这块区域释放还给操作系统,动态开辟出来的内存只能手动free释放或者在程序结束时自动释放。所以假如我们的程序时一个全日制的程序,24小时均进行工作,在使用完开辟好空间之后不主动进行释放的话就有可能造成内存泄露的风险。(所谓的内存泄漏也就是指堆区中的内存遗失,无法再次被找到,最终导致内存被占满,进而死机)如:
需要特别指出的是在使用所有动态内存分配函数的时候均需要引入一个头文件
我们先来观察下面一段代码进而感受以下 malloc 和 free 函数的使用方法:
#include
#include
int main()
{
//向堆区中申请一块大小为40个字节的空间
int* p = (int*)malloc(10 * sizeof(int));
//对malloc函数的返回值进行判空处理
if (p == NULL)
{
perror("main:");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p+i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p+i));
}
//释放使用后的空间
free(p);
p = NULL;
return 0;
}
我么可以观察上面的代码,会发现有许多细节的地方。
NUM 1:我们在用整形指针 p 接收 malloc 返回的指针的时候下一步操作会对 p 指针进行判空处理,这是因为,malloc 函数在开辟内存失败的时候会返回一个空指针即 NULL ,如果我们不对 p 进行判空处理的话就存在直接使用空指针的可能。而对空指针进行解引用会使我们的程序异常,导致程序崩溃。如:
NUM 2:在我们的使用达到目的时(打印出 0-9 数字之后)立即释放动态开辟的内存,防止因为以往造成内存泄漏(养成良好的习惯)。
NUM 3:在释放完内存之后将用于接收 malloc 返回值的指针手动置成空指针。防止野指针的出现,造成异常访问。
上面就是我们的 malloc 函数和 free 函数的主要使用,接下来我们来接着认识一下 calloc 函数。
calloc 函数其实与我们的 malloc 函数很相似,只不过参数等方面有些变化。同样的我们先从calloc 的函数原型进行认识该函数。
我们可以看到 calloc 函数返回类型和 malloc 函数相同都为一个指针,这个指针也同样符合malloc 函数的返回原则,即成功开辟空间返回首地址,开辟失败返回 NULL 。但是我们会发现 calloc 函数在参数上和 malloc 有些许差异。
实质上 calloc 函数的功能是在堆区上开辟一块元素个数为 num 个,元素所占空间大小为 size 字节的空间。(我们可以将其认为时 malloc 函数参数的分解)如:我们需要开辟十个整形,用 malloc 函数表达的形式是 malloc(40),而用 calloc表达就是 calloc(10,4)。
还有一点 calloc 与 malloc 函数具有明显的区别,那就是 malloc 虽然可以进行空间上的开辟,但是不能对空间上各元素进行初始化,而我们的 calloc 函数为我们开辟了一段连续的空间之后还会自动将我们的数据初始化为0。
我们可以通过一段代码来体现 calloc 与 malloc 函数的相同之处与不同之处:
// malloc函数
//#include
//#include
//int main()
//{
// int* p = (int*)malloc(10 * sizeof(int));
// if (p == NULL)
// {
// perror("main");
// return 1;
// }
// int i = 0;
// for (i = 0; i < 10; i++)
// {
// printf("%d\n", *(p + i));
// }
// free(p);
// p = NULL;
// return 0;
//}
//calloc函数
#include
#include
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("main");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
我们通过上面的运行截图也可以发现两者之间的区别。其余方面两函数均相同。再次声明请注意以下几点:
NUM 1:对接收返回值的指针进行判空操作!!
NUM 2:使用完函数时对动态开辟的空间进行释放!!
毕竟重点说几遍都不算多,那么我们来继续来介绍我们最后一个内存开辟函数——realloc。
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。老规矩我们先从函数原型进行了解。
我们首先来认识一下这个函数的参数和返回值(和我们之前认识到的 malloc 和 calloc 函数区别可不小哦):
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直 接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够大的空间,原有空间之后没有足够多的空间 时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来 使用。这样函数返回的是一个新的内存地址,之前开辟的空间自动 被系统释放。
情况3:找不到合适的内存进行开辟返回空指针即 NULL 。
假如为第二种情况,realloc 函数还会将原来内存中的数据移动到新的空间。
特别指出一点:为了防止 relloc 函数返回空指针导致原先指针位置丢失,所以应 当先使用新建的指针进行接受进行判空处理之后,再赋给 p 。
我们通过一张图进行理解这三种情况。
同样的我们先了解了 relloc 函数的大致用法接着通过具体的例子进一步了解该函数的使用方法。
#include
#include
int main()
{
//在堆区上开辟40个字节大小
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//将上面开辟的内存大小拓展成80个字节
int* pstr = (int*)realloc(p, 20 * sizeof(int));
if (pstr == NULL)
{
perror("realloc");
return 2;
}
p = pstr;
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
我们可以通过上面的代码知道,当我们第一次开辟的内存假如不够用的时候可以用 realloc 函数重新调整内存块的大小(上面代码中由40个字节调整至80个字节)。同样的我们也可以通过监视窗口观察我们上面所说的三种情况。
第一种情况存在足够的空间直接在后面扩展。返回指针不改变。
第二种情况 p 后并不直接存在可用空间寻找并开辟新的空间,返回新的指针。
第三种情况找不到合适的空间,返回空指针。
经过我们上面的验证,相信大家已经对 realloc 函数已经基本上了解了。那么我们再来举一个特殊一点的例子:malloc 和 realloc 的等价互换。
当 ralloc 函数中的指针参数为 NULL 时,其作用等价于 malloc 即 realloc(NULL,40)等价于 malloc(40)。都是在堆区中开辟一个大小为40个字节的空间以供我们使用。
那么以上就是我们本次博客的全部内容了,感谢大家的观看,祝大家天天开心。