文章目录
前言
在动态内存开辟(上)中我向大家介绍了malloc、calloc以及介绍动态内存常见的错误。那么在本文中,我将继续给大家介绍另一个非常重要且实用的动态内存管理的函数——realloc函数,以及再深入探讨一下free函数的使用细节,避免在使用动态内存函数时,由于不及时释放或者时胡乱释放所造成不必要的麻烦出现。
1. realloc
C语言中还有一个用于动态内存开辟的函数——realloc函数,其功能十分的重要。
为什么为会这么说呢?
这就要与realloc函数的功能挂钩了。大家不妨回想一下,无论是malloc或者是calloc申请的动态内存空间一旦申请了之后就无法再调整空间的大小(除非自己设置一小段代码,进行调整)。比如:有时我们发现过去申请的空间太小了,有时候又觉得申请的空间太大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的处理。realloc函数就可以做到对动态内存开辟的空间进行调整。
realloc函数的功能:调整动态开辟后的空间大小。
realloc具体细节如下:
函数原型:
void* realloc (void* ptr, size_t size)
- ptr是指向要调整的内存起始地址
- size是表示要内存调整之后总的大小,单位为字节
- 返回值为调整之后内存空间的起始地址
- 这个函数调整原内存空间的大小的基础上,还会将原来内存中的数据移动到新的空间中(也就是realloc申请的内存中)。
- 不过要注意(记住):realloc函数在调整空间时,存在两种情况:
-
- 情况1:原有空间之后有足够大的空间
-
- 情况2:原有空间之后没有足够大的空间
2. realloc函数在调整空间时的细节
为了方便大家的理解,我用图解的方式再展示一遍realloc在调整空间时的情况。
2.1 针对情况1(realloc后面有足够的内存空间)
当是情况1的时候,要拓展内存就直接在原有的空间之后直接追加空间,原来空间的数据不会发生改变。
一句话概括:realloc后面的空间足够的话,直接在其后面调整内存大小即可。
2.2 针对情况2(realloc后面没有足够的内存空间)
当是情况2的时候,原有空间之后没有足够多的空间时,拓展的方法是:在堆空间上寻找另一个合适大小的连续空间来使用。这样函数返回的是一个新的内存起始地址。
2.3 realloc函数使用的注意事项
由于上述的两种情况,realloc函数在使用时就得注意一些细节的问题:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *ptr = (int*)malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
return 1;
}
//扩展容量
//代码1 - 直接将realloc的返回值放到ptr中
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
}
我们一般使用realloc函数调整空间大小时,不会直接将要调整空间的起始地址作为接收realloc函数的返回值。因为,如果realloc函数在申请申请空间失败之后,会返回一个NULL值,也就说不仅realloc申请失败了就连之前申请的那块空间也找不到了。这就会造成严重的信息丢失的问题。
2.4 realloc的使用实例
题目:加入之前我创建的10个整型的数据空间不够用了,我想扩容到20个整形数据的大小,并插入数据。
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
perror("malloc fail");
return 1;
}
//向申请空间里面填入1~10的数据
for (int i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//到这里,突然我想扩容了,扩大到20个字节的大小,另外在这些数据的后面接着填入11~20的数据
//----------------------------------------
int* ptr = (int*)realloc(p,sizeof(int)*20);//只有强制类型之后我们才能随意操作这块内存空间
if (ptr == NULL)
{
perror("realloc fail");
return 1;
}
p = ptr; //这步得记住
//-----------------------------------------
for (int i = 10; i < 20; i++)
{
*(p + i) = i + 1;
}
for (int i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
上述的一些代码的具体细节需要读者们养成良好的编程习惯。多敲多练。
2.5 realloc函数的补充
realloc的函数原型为void* realloc(void* ptr, size_t size)
。可能有些脑洞大开的读者就会想,如果我给形参ptr的值为NULL会发生什么情况。
给realloc形参中的ptr赋值为NULL就相当于malloc。也就是说,
realloc(NULL,40) == malloc(40)
还有就是,realloc函数在调整空间时属于第2种情况的话,其会自动释放之前的那块空间,不需要我们再手动释放了。 这点细节希望读者们牢记。
3.动态内存常见的错误
相信有不少读者在刚接触到动态内存管理时,总是会用出花来,而这些情况往往也是导致程序出现崩溃的罪魁祸首。
那就让我们来看看,有多少种我们常见的动态内存的错误用法。
3.1 对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值为NULL,就会出现问题
free(p);
}
3.2 对动态开辟的空间的越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(int i = 0;i <= 10;i++)
{
*(p+i) = i;//当i为10的时候就会出现越界访问的情况
}
free(p);
p = NULL;
}
3.3 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p);//这个就是错误的,因为指针p指向的不是动态内存开辟的空间
}
3.4 使用free释放一块动态开辟内存的一部分
void test()
{
int*p = (int*)malloc(100);
p++;
free(p);//此刻的p不再是指向动态内存的起始地址了
}
3.5 对同一块内存空间多次释放
void test()
{
int *p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
3.6 动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
whlie(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
切记:动态开辟的空间一定要及时释放,并且是正确的释放。
4.总结
在本文中,我们学习到了realloc的各种细节,以及动态内存常见的错误。希望读者们以后在写代码时尽量避免这些错误,否则程序就会陷入到水深火热之中。
结合动态内存管理(上)的内容,我们已经清楚的知道了动态内存开辟是怎么一回事了。总而言之,就是玩明白malloc、calloc、realloc、free这几个函数。