前言:哈喽小伙伴们,虽然已经放假啦,但依然阻止不了我们学习的脚步。
超越别人的唯一的方法,就是比别人更加努力。
今天我们一起来学习——动态内存管理,掌握如何自己操纵数据空间的大小。
我们已经学习过两种内存的开辟方式:
一是创建单个变量:int a = 10;
二是创建数组:int arr[10] = {1,2,3,4,5,6,7,8,9,10};
但是这些方式有个特点,那就是一旦创建之后,我们的内存大小就无法改变。
这样很容易出现内存不足或者内存开辟过多而浪费的情况。
所以为了应对这种问题,C语言让我们程序员能够进行——动态内存管理。
那我们管理动态内存的方式,便是通过使用四个函数:
malloc
free
calloc
realloc
下面我们就来逐一讲解它们的结构和用法。
使用动态内存函数,都需要头文件:#include
可以看到,这个函数的返回类型为指针类型,参数为无符号整型。
size代表的是字节数,也就是说,malloc函数是用来申请一定字节数内存空间的函数。
下面我们来尝试使用一下malloc:
#include
#include
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
return 0;
}
比如说,我们现在要开辟10个整型大小的内存空间,也就是40个字节。
注意我们的参数要写成上述的这种形式,可以增加代码的可读性,尽量不要直接写成40。
那么既然是开辟整型空间,就要用整型指针来及时接收,同时因为malloc的返回类型为void*,还要进行强制类型转换。
p指针会指向malloc函数所开辟的空间的起点,直到10个int类型空间为止。
这个时候,p就可以作为一个数组去使用了,为开辟的空间填上数据。
#include
#include
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
return 0;
}
但是有一个问题,我们的内存空间是有限的,而且malloc所选择开辟的空间是随机的,它并不会挑一块很大的没有被占用的空间给你开辟。
这就说明,有可能我们所开辟的空间已经被占用了,这样就会导致开辟失败。
malloc申请开辟内存成功,就会返回空间的起始地址,如果申请失败,则会返回空指针NULL。
我们是不能够对一个空指针进行使用的,所以我们创建动态内存空间的同时,还要进行判断,如果开辟失败,则直接结束程序运行。
#include
#include
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
return 0;
}
main函数的正常返回值为0,这里我们用1来表示异常返回。
我们既然是自己申请的空间,用过之后则需要及时将此空间进行释放,那么又该怎么释放呢?
那么既然程序退出就会自动释放,为什么还需要free函数呢???
实际上,如果让系统自动释放,存在很大的不确定性:
比如程序实际上并没有退出,或者内存空间出现问题而无法释放等等,这样就会造成严重后果。
靠别人不如靠自己,所以我们申请空间之后一定要通过free函数来主动释放。
能够看出free函数的参数是一个指针类型,也就是释放这个指针所指向的全部空间。
#include
#include
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
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);
}
那我们就借用上边的代码来使用一下free函数,来看看它具体是如何释放:
能够看出,free将p指针指向的空间确实释放了,但是它并不会改变p指针所指向的位置。
这就类似与一个人突然失去了记忆,但是他还是这个人,只是什么都没有了。
这样就会导致p成为野指针,我们知道野指针是非常危险的,不能让它存在。所以我们在释放空间之后,紧接着要将p指针置空。
free(p);
p = NULL;
同时这里我们还要提醒两点:
calloc函数也是用来申请动态内存空间的,但是与malloc有些许不同。
calloc函数有两个参数,实际上,这两个参数可以认为是malloc函数的参数的分解。
size表示要申请的空间大小
num表示要申请的空间个数
比如说我们还是申请10个整型数据空间:
int* p = (int*)calloc(10,sizeof(int));
这样来看calloc和malloc实际上是一样的,但是它们还有另一个不同:
#include
#include
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
来看这个代码,我们用malloc函数申请完空间之后,不做任何处理,来看看打印出来的数据:
是随机值,但是当我们把malloc换成calloc时:
#include
#include
int main()
{
int* p = (int*)calloc(10 , sizeof(int));
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
得到结果:
全为0,这就说明calloc在开辟空间时,会进行初始化,而malloc并不会。
realloc函数的作用,是帮助调整由malloc和calloc开辟的空间大小。
realloc函数有两个参数:
ptr为指针,也就是用于接收malloc或calloc开辟空间的那个指针
size则是要调整的新的空间大小
具体使用如下:
#include
#include
int main()
{
int* p = (int*)calloc(10 , sizeof(int));
if (p == NULL)
{
return 1;
}
p = realloc(p, 20 * sizeof(int));
free(p);
p = NULL;
return 0;
}
但实际上,我们上边的代码存在问题,那就是realloc的返回值不能直接用p接收,因为它也会出现内存开辟失败的问题。
如果开辟失败,同样会返回一个空指针,这样不仅没有调整成功,反而原先的空间也不复存在了。
所以我们新建一个指针来接收,并判断是否为空指针,不为空则将新指针赋值给p指针。
#include
#include
int main()
{
int* p = (int*)calloc(10 , sizeof(int));
if (p == NULL)
{
return 1;
}
int* p1 = realloc(p, 20 * sizeof(int));
if (p1 != NULL)
{
p = p1;
}
free(p);
p = NULL;
return 0;
}
此外,realloc函数还有一个功能,那就是它其实也可以作为malloc函数使用。
当realloc函数的第一个参数为空指针NULL时,其功能等于malloc函数。
realloc(NULL,sizeof(int)) == malloc(sizeof(int))
当然这样使用之后还是要进行空指针的判断和使用后的释放操作。
有关动态内存管理的知识到这里就结束啦,希望能够对大家有所帮助。
喜欢博主文章的小伙伴不要忘记一键三连呀!!!
祝大家国庆节快乐!!我们下期再见!!