目录
一、为什么存在动态内存管理?
二、C语言中动态内存管理方式
1、函数介绍(malloc、calloc、realloc、free)
1)先来看malloc函数
2)calloc函数
3)realloc函数
2、动态内存管理中的错误操作
int var = 10;
int arr[10];
上面变量及数组的空间开辟为固定的大小:分别是 4 字节和 40 字节。
1、如果我们在为像栈或是链表这样的数据存储结构开辟空间时,就有可能会在空间不足的情况下为其开辟空间,那么就会涉及到动态内存的开辟。这也就是动态内存管理存在的原因之一。
2、换言之,有的时候我们并不能一次性的开辟好需要的空间大小,如果一次性开辟空间太大会造成空间的浪费,开辟的太小又会不太够用;这时候我们就希望可以有一种动态的方法:当需要使用内存时,需要多少? 立马为其开辟多少,不需要我就不开辟空间,这样既可以满足需求还可以尽可能少的避免造成空间的浪费。
前三个函数的使用往往会与free函数搭配起来使用,前者是在内存上开辟所需空间提供使用,后者是在对空间使用结束后释放、归还系统的。
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
★ 如果开辟成功,则返回一个指向开辟好空间的指针。
★ 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
★ 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自
己来决定。
#include
#include
int main()
{
int arr[10] = { 0 };
int* ptr = NULL;
ptr = (int*)malloc(10 * sizeof(int));
if (NULL == ptr) //判断ptr指针是否为空
{
printf("malloc fail!\n");
exit(-1);
}
int i = 0;
for (i = 0; i<10; i++)
{
*(ptr + i) = i;
}
for (i = 0; i<10; i++)
{
printf("%d ", *(ptr + i));
}
printf("\n");
free(ptr); //释放ptr所指向的动态内存
ptr = NULL;
return 0;
}
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free函数用来释放动态开辟的内存:(在上面的代码也有所体现)
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
void* calloc (size_t num, size_t size);
★ 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
★ 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include
#include
int main()
{
int* ptr = NULL;
ptr = (int*)calloc(10 , sizeof(int));
if (NULL == ptr) //判断ptr指针是否为空
{
printf("malloc fail!\n");
exit(-1);
}
int i = 0;
for (i = 0; i<10; i++)
{
printf("%d ", *(ptr + i)); // 全为0
}
printf("\n");
free(ptr); //释放ptr所指向的动态内存
ptr = NULL;
return 0;
}
输出结果: 0 0 0 0 0 0 0 0 0 0
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型如下:
void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址,size 调整之后新大小,返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
此时扩展内存会在原有空间的后面直接增加,原内存存储数据不变。
#include
#include
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(10 * sizeof(int));
if (NULL == ptr) //判断ptr指针是否为空
{
printf("malloc fail!\n");
exit(-1);
}
int i = 0;
for (i = 0; i<10; i++)
{
*(ptr + i) = i;
}
int* ptr1 = NULL;
ptr1 = (int*)realloc(ptr, 80);
if (NULL == ptr1) //判断ptr指针是否为空
{
printf("realloc fail!\n");
exit(-1);
}
for (i = 0; i<10; i++)
{
*(ptr1 + i + 10) = i+10;
}
for (i = 0; i<20; i++)
{
printf("%d ", *(ptr1 + i));
}
printf("\n");
free(ptr1); //释放ptr所指向的动态内存
ptr1 = NULL;
return 0;
}
上述代码realloc在原来malloc开辟空间的基础上扩展空间,原空间之后有足够大的空间。所以指针ptr和ptr1的地址是相同的。如下图所示:
情况2:原有空间之后没有足够大的空间 此时会在堆空间寻找一处足够大小的连续空间,并返回此处空间的地址。
当realloc在原来空间的大小上新开辟的空间太大时,即原有空间之后没有足够大的空间 ;此时会在堆空间寻找一处足够大小的连续空间,并返回此处空间的地址。同时也会释放原空间。(将上面代码段中realloc开辟空间的大小调的大点可以验证)
在动态内存管理中所开辟的空间使用结束后需要对其进行释放操作,目的是为了将空间还给内存供系统调用。但需要注意以下错误操作:
1)对NULL进行解引用。
2)越界访问开辟的空间。
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(10 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
printf("%d\n", *(ptr + 5)); // 正确访问
printf("%d\n", *(ptr + 15)); // 越界访问
return 0;
}
3)对非动态开辟的空间进行free操作。
4)只释放部分动态开辟空间。
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(10 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
ptr++;
free(ptr);
ptr = NULL;
return 0;
}
5)对同一动态开辟的内存空间多次释放。
6)忘记释放动态开辟的内存空间。(内存泄漏)
结论:动态开辟的内存空间使用结束后一定要释放,并且要正确释放。
★★★★★ 感谢阅读!!!