前言
练习使用动态内存相关的4个函数,并调试观察
malloc、calloc、realloc、free
1.malloc
malloc函数在内存堆区申请一块空间,并返回指向这块空间的地址;
如果开辟失败,则返回NULL;
可以将malloc开辟的这块空间类比成一个数组,即我们利用malloc在堆区中开辟了一个数组;
我们可以看到,malloc函数的返回值是一个void*类型的指针(该指针指向这块空间的首元素地址),所以我们可以开辟的空间可以是任意类型,即我们可以开辟出一个任意类型的数组;
如下,如果我们想要开辟处一个int类型的空间:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
int* arr = malloc(5 * sizeof(int));
for (i = 0; i < 5; i++)
{
*(arr + i) = i + 1;
}
for (i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
运行如下:
这里malloc中传入的参数是字节数,在这里其实就能决定了我们开辟空间的类型,那我们就有了一个小技巧:
如果我们已经确定要开辟num个(类型)的空间,那我们传入的参数就可以写成num*sizeof(类型名);
但是很明显,这段代码是存在问题的,下面我们就来介绍一下
1.1.malloc函数的注意事项
我们知道,malloc会在内存中开辟处一块空间,但是malloc不是一定能开辟成功,例如:
此时malloc中的参数是一个非常非常大的数,在这里我们可以看到,当需要的空间过大或者一些其他原因,malloc可能就无法成功开辟,返回的是NULL,那这个返回值就无法正常使用;
因此
malloc的返回值一定要检查,因此,最开始的那一段代码应当加上检查部分:
int main()
{
int i = 0;
int* arr = malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("malloc");
return 1;
}
for (i = 0; i < 5; i++)
{
*(arr + i) = i + 1;
}
for (i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
一旦开辟不成功,perror就会帮我们指出来,这个时候应该提前结束程序:
2.calloc
在介绍完malloc之后,我们再来看一个和malloc函数的作用几乎相似的函数——calloc,这个函数同样是在内存中开辟一块空间,但传入的参数有两个,num和size,即num个size大小的空间,但他和malloc函数最大的不同点便是calloc会对开辟的空间进行初始化,如下:
int main()
{
int* arr = calloc(5, sizeof(int));
if (arr == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
要注意,calloc函数也需要对返回值进行检查,他也不是一定能成功开辟出空间;
在实际应用的过程中,我们可以根据自身需要选择使用malloc函数或者是calloc函数,但calloc函数比malloc函数多出一个步骤,其执行速度必然是比不上malloc的;
3.free
C语言还提供了一个函数free,专门用来动态内存的释放和回收;
如果参数 ptr 指向的空间不是动态开辟的,那free函数的⾏为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
我们直接来看一段代码
int main()
{
int i = 0;
int* arr = malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("malloc");
return 1;
}
for (i = 0; i < 5; i++)
{
*(arr + i) = i + 1;
}
for (i = 0; i < 5; i++)
printf("%d ", arr[i]);
printf("\n");
free(arr);
arr = NULL;
return 0;
}
这段代码与上面代码唯一不同的地方便是加上了free,当我们用完malloc函数开辟出来的空间之后,应该将他还给内存;
其实在程序结束后,系统也会自动回收我们开辟的内存空间,那这个函数的用处体现在什么地方呢?我们再来看这一段代码:
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
不是所有的程序都会结束,现在几乎所有的大企业,他们公司的程序可能是一天24小时不间断地走,像这一段代码,他陷入了循环,整个程序就不会结束,当程序持续执行的时候,我们申请的内存空间却还是占用着内存的空间,这难免会造成一些不良的影响;
内存真的很宝贵,系统中的每一处内存都有其必要的用处,当我们申请完一处内存空间后,如果不将其回收,他就会一直储存在我们的内存中,这种情况称为内存泄漏,我们申请的空间越来越多,但却没有将它的使用权限还给系统,那这样我们的服务器就会越来越卡,直到最后就会崩掉,所以为什么要释放掉,就是这个原因;
因此:malloc和free是要成对出现的;
在此基础上,我们还要再注意一个点,我们来看一段代码和他的运行结果:
我们可以发现,free前后pa的值是一直没有改变的,但是经过释放,我们已经把pa所指向的空间还给了内存,那这块空间就无法使用,当我们写的代码过多,我们或许又会不小心使用到pa,那这个时候会发生什么呢?
我们通过一段代码和他的调试来更清晰地了解这个过程:
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL)
{
perror("malloc");
return 1;
}
int* pa = arr;
int i = 0;
for (i = 0; i < 5; i++)
{
arr[i] = i + 1;
}
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
这是开辟后和赋值的全过程,那这个时候我们想打印出开辟出内存中的值:
没有问题;
那在使用free释放之后呢,我们先看右边调试时内存中的值的变化;
在释放之后,内存中的值就已经全部改变了,因为它已经被内存回收,这块空间的使用权限已经不属于我们了,所以我们就不可能将其再次使用并打印出来;
所以说,在每次使用完free之后,一定要将其置空,免得程序使用故障;
如下;
free(arr);
arr = NULL;
这就是free有关的内容,我们再来看一下realloc函数的介绍:
4.realloc
realloc函数的出现让动态内存管理更加灵活
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使
用内存,我们⼀定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
下面来画一张图供大家理解:
这里需要注意,realloc同样不是一定能够成功开辟的,开辟不成功时同样会返回NULL,因此在使用realloc的时候,最好创建一个临时变量接收其返回值,并对其进行验证,开辟成功后再将其放到用来维护他的变量中,这样可以保证在开辟空间失败后,原有的数据也不会损失;如下:(还是想让arr继续维护这一块空间):
int main()
{
int* arr = (int*)malloc(5 * sizeof(int));
int* ptr = (int*)realloc(arr, 10 * sizeof(int));
if (ptr != NULL)
{
//......
arr = ptr;
ptr = NULL;
}
//......
return 0;
}
C语言有了动态内存开辟,程序员的发挥就更加灵活,我们在平常学习C/C++的时候,一定要熟练掌握动态内存的开辟;以上就是这篇文章的全部内容。