1、动态内存分配存在的原因
我们普遍定义变量的时候,经常采用以下的写法:
char str;
int num;
int arr[10];
但是上述的开辟空间的方式有两个特点: 1、空间开辟大小是固定的。 2、数组在声明的时候,必须指定数组的长度,他所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。
2、动态内存函数的介绍
- C语言提供了一些动态内存开辟的函数:使得存储空间的使用更加灵活,提高内存的利用率。
- malloc和free都声明在 stdlib.h 头文件中。所以在使用的时候要引相应的头文件,下面让我们来看一下这些函数的用法。
2.1malloc函数
1、malloc函数的介绍:这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
void* malloc(size_t size);
向内存堆区申请size个字节的空间,申请完毕后返回堆区的地址,因为不知道返回什么类型的地址合适,所以返回void*类型的。 2、如果开辟成功,则返回一个指向开辟好空间的指针。
3、如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
4、返回值的类型是 void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。一般情况下不拿void*来接收,要强制类型转换后进行使用。
5、如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
6、在使用malloc开辟空间的时候,一定要判断是否开辟成功
int main()
{
int *p=(int*)malloc(40);
if(p==NULL)
{
return -1;
}
int i=0;
for(i=0;i<10;i++)
{
*(p+i)=i;
}
}
我们申请空间,使用空间,在使用完成后我们要释放空间,此时就用到了free函数。
2.2free函数
C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下: 1、free函数的介绍:
void free (void* ptr);
free函数用来释放动态开辟的内存。 2、free的使用:
int main()
{
int *p=(int*)malloc(40);
if(p==NULL)
{
return -1;
}
int i=0;
for(i=0;i<10;i++)
{
*(p+i)=i;
}
free(p);
p=NULL;
}
free把p的空间给释放掉了,但是不会改变p的地址。这种释放后依然能找到free释放的空间是非常可怕的,所以要将释放的空间置空。
3、如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。free也不知所措了。
4、如果参数 ptr 是NULL指针,则函数什么事都不做。所以可以给free传一个空指针,但是free什么事都不会干。
2.3calloc函数
C语言还提供了一个函数叫 calloc , calloc函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
1、calloc函数介绍:num表示元素的个数,size表示一个元素有多大,单位字节
#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
int *p=(int*)calloc(10,sizeof(int));
if(p==NULL)
{
printf("%s\n",strerror(errno));
return -1;
}
free(p);
p=NULL;
return 0;
}
2、malloc函数只负责在堆区申请空间,并且返回起始地址,不初始化内存空间;calloc函数在堆区上申请空间,并且初始化为0,返回起始地址。
3、如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
2.4realloc函数
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。函数原型如下:
void* realloc (void* ptr, size_t size);
1、realloc函数的介绍:ptr是要调整的内存地址,之前可能有malloc或者calloc开辟好的空间;size 为调整之后新大小,单位字节。
2、返回值为调整之后的内存起始位置。
3、realloc函数的出现让动态内存管理更加灵活。
4、这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
5、realloc在调整内存空间的是存在两种情况:
1)情况1:原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。把原先空间的地址当作新地址返回。
2)情况2:原有空间之后没有足够大的空间,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。并将原空间的数据拷贝在新空间上,在释放掉原空间,这样函数返回的是一个新的内存地址。
6、 由于上述的两种情况,realloc开辟失败会返回空指针,如果贸然使用,开辟失败可能会丢失原有空间,所以realloc函数的使用就要注意进行判断,在进行自身的赋值。 举个例子:
#incldue<stdio.h>
#include<errno.h>
#include<string.h>
int main()
{
int *p=(int*)calloc(10,sizeof(int));
if(p==NULL)
{
printf("%s\n",strerror(errno));
return -1;
}
int* ptr=(int*)realloc(p,20*sizeof(int));
if(ptr!=NULL)
{
p=ptr;
}
free(p);
p=NULL;
return 0;
}
3、常见的动态内存错误
3.1对NULL空指针的解引用操作
int main()
{
int *p=(int*) malloc(20);
*p=0;
if(p==NULL)
{
return -1;
}
}
结论:所以对动态内存开辟的空间一定要先判断在使用。如下:
int main()
{
int *p=(int*) malloc(20);
if(p==NULL)
{
return -1;
}
*p=0;
}
3.2对动态开辟空间的越界访问
int main()
{
int *p=(int*)malloc(200);
if(p==NULL)
{
return -1;
}
int i=0;
for(i=0;i<80;i++)
{
*(p+i)=i;
}
free(p);
p=NULL:
return 0;
}
结论:在对动态开辟的内存使用时,一定要注意范围。
3.3对动态开辟空间的越界访问
int main()
{
int a=10;
int* p=&a;
free(p);
p=NULL;
return 0;
}
结论:对非堆上的内存不可以使用free释放。
3.4使用free释放一块动态开辟内存的一部分
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;
}
free(p);
p=NULL;
return 0;
}
结论:++/–在某些情况下是具有副作用的,对于地址的起始位置是非常重要的,如果要用到地址的偏移,可以先保存原先地址的起始位置。即给原地址进行一个备份。
3.5对同一块内存的多次释放
int main()
{
int* p=(int*)malloc(40);
if(p==NULL)
{
return -1;
}
free(p);
free(p);
return 0;
}
另一种错误:
int main()
{
int* p=(int*)malloc(40);
if(p==NULL)
{
return -1;
}
free(p);
p=NULL;
free(p);
return 0;
}
结论:对释放的空间一定要进行及时的置空,并且不能重复释放同一空间。即使有两个指向同一内存的,也只能free一个,因为释放完之后,原有的内存空间已经释放掉了,在进行释放就是重复释放。
4、后续有动态内存的面试题及其运用,收藏+关注不迷路哟。
|