哈喽小伙伴们,本期文章为大家讲解的是动态内存的管理,一起来看看吧~
目录
一.为什么会存在动态内存分配
目前我们以掌握的内存分配有两种:一种是以变量的形式开辟; 另一种是以数组的形式开辟
int a;//以变量的形式在栈区上开辟连续4个字节的空间
int arr[10];//以数组的形式在栈区上开辟连续40个字节的空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了.这时候就需要试试动态内存了.
二. 动态内存函数
动态内存函数有四个,分别是malloc,free,calloc,realloc,他们的头文件都是<stdlib.h>
1.malloc
malloc的函数原型
void* malloc(size_t size);//size是想要开辟空间的大小(多少个字节)
malloc的具体使用方法:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(20);
//ptr=(int *)malloc(5*sizeof(int));//两种写法都可以
if (ptr==NULL)//判断是否开辟成功,成功ptr!=NULL,不成功ptr=NULL
{
printf("%s\n", strerror(errno));//输出开辟失败的原因
}
return 0;
}
malloc这个函数在内存中申请的是一块连续可用的空间 ,开辟成功后返回一个指针.
如果开辟成功,返回的是一个指向这块开辟好的空间的首地址的指针;
如果开辟失败,则返回一个NULL指针.
因此,malloc的返回值一定要做检查!!!可以用strerror()函数和errno()函数来检查,他们的头文件分别是<string.h>和<errno.h>
malloc的返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,所以`在使用的时候要将其返回值强制类型转换为自己实际需要的数据类型。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
2.free
free()函数专门是用来做动态内存的释放和回收的,函数原型如下:
void *free(void *p);
free的使用方法很简单:
int main()
{
int* ptr = NULL;
ptr = (int*)malloc(20);
//ptr=(int *)malloc(5*sizeof(int));
if (ptr==NULL)
{
printf("%s\n", strerror(errno));
}
free(ptr);
return 0;
}
但是这个并没有将ptr这个指针完全释放为什么呢?让我们来看看free()前后,ptr在内存中的变化
因此,我们在free()之后,一定要将该指针置为空(即NULL).
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是非法的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
3.calloc
calloc函数也是用来分配内存的,它的原型如下:
void* calloc (size_t num, size_t size);
它的使用方法和malloc很类似:
#include<stdio.h>
#include<stdlin.h>
int main()
{
int *p=NULL;
p=(int *)calloc(5,sizeof(int));
if(p==NULL)
{
printf("%s\n",strerror(errno));
}
free(p);
p=NULL;
return 0;
}
但是他们之间也有所差别:
1.参数不一样
2.虽然他们都是在堆区开辟空间,但是malloc函数不初始化,而calloc函数会初始化为0
4.realloc
当我们开辟空间后,有时我们会觉得申请的空间不够,有时候又会觉得申请的空间过大了,这时候,我们可以对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。它的原型如下:
void* realloc (void* ptr, size_t size);//ptr 是要调整的内存地址,size 调整之后新大小
realloc函数的使用方法:
int main()
{
int* p = (int*)malloc(10);
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
int *ptr=(int *)realloc(p, 100);
if (ptr == NULL)
{
printf("扩容失败:%s\n", strerror(errno));
}
p = ptr;
free(p);
p = NULL;
}
realloc函数的返回值为调整之后的内存起始位置,这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
这种情况下,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间之后没有足够的空间,
这种情况下,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
所以,当我们在扩容时,最好时新建与一个临时变量来接收,因为扩容时可能会失败,如果这时候还是用原来的的指针变量来接受,就会变成一个空指针,就会导致原来的数据丢失,这就得不偿失了.如果觉得换了新变量对后续的操作不方便,我们可以将新创建的指针赋给原来的指针变量,这样就可以继续使用了,也不会发生原来的数据丢失问题.
扩容失败的原因是什么呢?如果你申请的空间过大就很有可能扩容失败
比如我们来看看,这是运行上面代码的运行结果,没有任何问题
那么如果我们将申请的空间大小扩容至一个很大的数值,比如说1000000000000,会出现什么结果呢?让我们运行程序来看看结果
编译器会很直接的告诉我们没有足够的空间,所以我们在扩容的时候不要无止境的扩,它是有一定限度的.
三. 常见的动态内存错误
1.对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
2.对动态开辟空间的越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
}
int main()
{
test();
return 0;
}
这段代码会有问题吗?让我们来看看运行结果
可以看到,运行这段代码后,程序会直接崩溃.因此,我们在写代码的时候,一定要注意开辟的空间大小,不要出现越界访问的情况
3.对非动态开辟空间使用free释放
下面看看这段代码
void test()
{
int a = 10;
int* p = &a;
free(p);//ok?
}
int main()
{
test();
return 0;
}
这段代码的运行结果会不会出错呢?
程序一直在运行,没有结束.所以,对非动态开辟空间是非法的
4. 使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
这里的p指针在p++之后指向的地址就不再是开辟空间时的地址了,那么如果或此时释放p,就会使得有一部分空间没有被释放
5. 对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6 .动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏
如果没有在程序中释放内存,那么程序结束后,操作系统会自动回收,但是最好还是我们在使用完后动态空间后就自己释放内存.动态开辟的空间一定要释放,并且正确释放 。
如果文章哪里有什么错误,欢迎各位小伙伴积极留言指正~