1. malloc和free
为解决静态内存开辟存在的问题,C语言提供了一个动态内存开辟的函数:
malloc为memory allocation的简写,意为内存分配。
这个函数的作用是向内存申请一块连续可用的空间,并返回指向这块空间的指针。
比如,我申请40个字节的空间,以前我们可以用数组的方法,现在我们用malloc函数申请40个字节的空间:
//int arr[10];
int* p = (int*)malloc(40);
malloc函数有以下特点:
如果开辟成功,则返回一个指向开辟好空间的指针;
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查;
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定;
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
所以我们需要对malloc开辟的空间做检查:
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");//如果开辟失败,告知原因
exit(-1);//开辟失败直接退出
}
需要注意的是:
对于malloc函数申请的内存空间,当程序退出时还给操作系统;当程序不退出时,malloc动态申请的内存不会主动释放。
所以这时C语言提供了另外一个函数free,专门用来做动态内存的释放和回收:
用法如下:
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");//如果开辟失败,告知原因
exit(-1);//开辟失败直接退出
}
free(p);
p = NULL;//避免p被释放后成为野指针
return 0;
}
需要注意的是:
free函数用来释放动态开辟的内存;
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的(也就是说free函数只能释放动态开辟的空间,不能释放静态开辟的空间);
如果参数 ptr 是NULL指针,则free函数什么事都不做。
(malloc和free都声明在stdlib.h头文件中)
2. calloc
C语言还提供了一个函数叫calloc ,calloc为contiguous allocation的简写,意为动态内存分配并清零,calloc函数也用来动态内存分配。
原型如下:
函数的功能是为 num个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0;
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
具体用法与malloc大致相同:
int main()
{
int* p = (int*)calloc(10, sizeof(int));//10个大小为4字节的元素
if (p == NULL)
{
perror("calloc");//如果开辟失败,告知原因
exit(-1);//开辟失败直接退出
}
//打印已开辟好的元素
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p);
p = NULL;//避免p被释放后成为野指针
return 0;
}
3. realloc
在我们使用malloc函数与calloc函数申请过空间之后,
我们可能会遇到申请的空间过大了,比如我动态申请了1000个字节的空间,可发现我只需要10个字节的空间;
又有可能遇到申请的空间过小了,比如我动态申请了100个字节的空间,可最后发现我却要10000字节的空间;
针对这两种现象,为了合理地使用内存,我们一定会对内存空间做灵活地调整,那 realloc 函数就可以做到对动态开辟内存大小的调整。
realloc函数原型如下:
解释:
1.ptr 是要调整的内存地址;
2.size 调整之后新大小;
3.返回值为调整之后的内存起始位置;
4.这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
关于第3条,我们抛出这样的疑问:
返回值为调整之后的内存起始位置,那么这个调整之后的起始位置,
①到底是realloc函数保留了原来malloc/realloc函数已经开辟好的空间,只是在原来内存的基础上紧挨着新增了一些空间,然后返回了原来旧内存空间的起始地址;
②还是realloc函数舍弃了原来malloc/realloc函数已经开辟好的空间,又再另一块地方重新开疆拓土,申请了一块新的内存空间,然后返回了新内存空间的起始地址。
对于这个问题,其实两种说法都对,我们要分两种情况讨论:
情况一:原有空间之后有足够大的空间。
情况二:原有空间之后的空间不足
1.开辟新的空间;
2.将旧的空间中的数据拷贝到新的空间;
3.释放旧的空间;
4.返回新空间的起始地址。
所以针对情况一和情况二,对realloc函数的正确用法应为:
int main()
{
//先用malloc开辟40个字节的空间
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");//如果开辟失败,告知原因
exit(-1);//开辟失败直接退出
}
//开辟成功后,将这块空间初始化为1~10
for (int i = 0; i < 10; i++)
{
p[i] = i + 1;
}
//再用realloc再增加40个字节的空间
int* ptr = (int*) realloc(p, 80);//未避免情况二的发生,不能用p接收新地址
if (ptr != NULL)
{
p = ptr;//如果开辟成功了,再把ptr拷贝给p
ptr = NULL;
}
else
{
perror("realloc");//如果开辟失败,告知原因
}
//打印数据
for (int i = 0; i < 20; i++)
{
printf("%d\n", p[i]);
}
//释放空间
free(p);
p = NULL;
return 0;
}
运行结果:
验证:
情况一:新开辟的空间比较小,后面有足够的空间,(新开辟4字节):
情况二:原有空间之后的空间不足(新开辟400字节)
(本篇完)