为什么要动态内存分配
我们较为熟悉的是以下两种存储方式,这种类型的存储方式,空间开辟空间的大小是固定的,但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,那么以上开辟空间的⽅式就不能满⾜了,因此引入动态存储,让程序员可以较为灵活的申请和释放空间。
int a=10;//栈上开辟四个字节的空间
int arr[10]={0};//在栈上开辟40个字节的连续空间
malloc、calloc和realloc的区别
malloc函数的形式如下:
void *malloc(size_t size)
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针,1.如果返回成功就返回起始地址,如果失败返回null指针,因此malloc的返回值一定要提前检查。2.函数的返回类型是void*所以开辟的空间由自己所决定。3.如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器。
free函数的形式如下:
void free(void*ptr)
此函数是⽤来做动态内存的释放和回收的。此外释放空间后还会记得所指向的位置,因此需要置空这样可以防止出现野指针。malloc和free最好是成对存在的。
realloc函数的形式如下:
void *realloc(void*ptr,size_t size)
realloc函数对动态存储进行调整,ptr是要调整的内存地址,size调整之后的大小,返回值为调整之后的内存起始位置。
realloc的存储情况比较特殊,情况1:原有空间之后有⾜够⼤的空间。情况2:原有空间之后没有⾜够⼤的空间。
下面我们将通过画图进行解释
calloc函数的形式如下:
void* calloc (size_t num, size_t size);
此函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
常见的动态内存的错误
对NULL指针的解引⽤操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);//malloc函数开辟失败会返回NULL
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
对动态开辟空间的越界访问
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);
}
对⾮动态开辟内存使⽤free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//err
}
使⽤free释放⼀块动态开辟内存的⼀部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
对同⼀块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();//调用之后应该进行释放
while(1);
}
忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。切记:动态开辟的空间⼀定要释放,并且正确释放。
动态内存经典笔试题分析
案例一:
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
此题有两处错误,下面进行解释1.str中存放的是NULL,p中存放是str传过来的地址,然后malloc进行开辟空间并指向p但是本身的str依然是空指针,因此会出现程序崩溃。2.malloc开辟空间后并没在函数使用后进行释放。
案例二:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
这个案例典型的返回栈空间地址问题,我们要知道对数组来来说,传回一个地址是会产生错误的。这里函数调用后返回了数组p的地址,但是开辟的空间在函数结束调用后就已经销毁了,所以这个空间是不存在的,地址传回是无用的,这样就会导致错误的发生。
案例三:
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
由图我们可以看出str是malloc开辟的空间这块空间并没有进行释放和置为空,导致内存泄漏。
案例四:
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
//str=NULL;
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
这个其实在free后就已经是野指针了,因为释放后空间还给操作系统了,后面就无法访问,那么strcpy就是非法访问。
总结
动态内存分配其实还是很考察对指针指向的理解,其次这个知识点对数据结构的学习有很大的影响。希望大家能重视起来,最后希望各位大佬能进行指正,共同进步。