在了解使用动态内存相关的四个函数之前,我们先了解一下,为什么要有动态内存分配?
首先,我们已经掌握了一种开辟内存的方式,就是直接使用int i=20;但是这样开辟空间有两个特点,1:空间开辟大小是固定的,2:数组在创建时,必须设定数组的长度,数组空间的大小一旦确定就不能更改
可以申请和释放空间,这样就⽐较灵活了
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,返回一个开辟成功的指针。
如果失败,则返回NULL。因此,在malloc使用之前必须检查是否为空指针。
返回类型为void*,因此,返回的类型由自己来决定。同时,size为0的时候,malloc的行为是标准未定义的,这取决于编译器。
free函数是专门用来做动态内存的释放和回收的。
void free (void* ptr);
free函数用来做动态内存的释放,如果ptr指向的空间不是动态内存,那么free的行为就是标准未定义的。如果ptr指向的空间为空,那么free什么事都不做。
malloc和free都声明在 stdlib.h 头⽂件中。
传递给free的是要释放空间的起始地址,例如下面的案例,传递的不是起始地址,就无法释放:
在free之后一定要置为空,否则会造成一些意想不到的灾难。
calloc的初始化如下:void* calloc (size_t num, size_t size); 它为每个大小为size的num字节开辟空间,并初始化为0。其与malloc的区别是它会将所有字节初始化为0。
有时会我们发现过去申请的空间太⼩了,有时候我们⼜会觉得申请的空间过⼤了,那为了合理的时 候内存,我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤ ⼩的调整。
函数的原型如下:
void* realloc (void* ptr, size_t size); ptr是要调整的内存地址,size为调整后的大小。返回值为调整后内存的起始位置。
这个函数调整原内存空间⼤⼩的基础上,还会将原来内存中的数据移动到新的空间。 realloc在调整内存空间的是存在两种情况: 情况1:原有空间之后有⾜够⼤的空间,空间续上,然后再返回起始空间的地址就可以了 情况2:原有空间之后没有⾜够⼤的空间
如果是情况一,增加空间直接追加在原有空间后面。 原有数据位置不发生变化,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。(满足新的大小要求)会将原来的数据拷贝一份到新的空间,释放旧的空间(realloc会主动把这块空间释放掉)。这样函数返回的是⼀个新的内存地址。
还有一种情况,调整失败,返回的是空指针。如下:
如果realloc调整失败了,空指针放到p里面,p原来还维护20个字节,现在20个字节释放了,也找不到了。
所以选择用新的指针ptr来接收新的空间地址
realloc函数可以完成和malloc一样的功能:
realloc(NULL,20);==malloc(20);
下面举一个失败的案例:
not enough space
动态内存的常见错误:
1对NULL指针的解引⽤操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
原因:malloc的返回值需要判断
修改:
void test()
{
int *p = (int *)malloc(INT_MAX/4);
if(p==NULL)
{
perror("malloc");
return 1;
}
assert(p);
*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释放,是不能够运行的
使⽤free释放⼀块动态开辟内存的⼀部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
必须是起始位置,报错。
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
在释放完之后,及时把p置为空指针。
动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
在test函数运行完成之后,就找不到malloc的一百个空间了。一旦出去,局部变量就销毁了。不用也不释放,就造成了内存泄漏的问题。
malloc,realloc,calloc申请的空间如果不想使用,都可以使用free来释放,当程序结束的时候,也会由操作系统释放。
尽量做到:谁申请的空间谁释放,如果不能释放,要告诉使用的人记得释放。
malloc和free成对出现。
但是架不住指针的空间可能提前释放,如下:
str为空指针。GetMemory本身是传值调用,p内也是NULL。malloc出空间的地址放到p指针中,p销毁,还给操作系统。当回来的时候,str依然为空指针。空指针没有指向有效的空间,
当结束最上面一个程序运行的时候,str的空间已经还给操作系统了,str已经是空指针了。出现对空指针的解引用操作,程序崩溃,不会打印。
那么如果对这个程序进行修改,使其正确呢?那么我们把str的地址传给GetMemory,char*是一级指针变量,那么要用二级指针接收
我们对修改后的程序,进行解释。
首先 创建一个指针,叫作str。str里储存一个空指针NULL。p指针里储存了一个地址,就是ptr的地址,指向ptr。而p malloc出100个空间的大小,所以相当于str指向这块空间。
这个代码有什么问题呢? p所指向的空间还给操作系统。
上面这个代码,应该free(str);之后把str置为空。
如上图所示,在free之后置为空。
柔性数组有哪些特点呢?
结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}
free(p);
return 0;
}
用realloc让数组变大变小
柔性数组的优势:
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{
int i;
int *p_a;
}type_a;
int main()
{
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));
//业务处理
for(i=0; i<100; i++)
{
p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;
return 0;
}
上述 代码1 和 代码2 可以完成同样的功能,但是 ⽅法1 的实现有两个好处: 第⼀个好处是:⽅便内存释放 如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给用户。用户调⽤free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能依靠用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉。 第⼆个好处是:这样有利于访问速度 连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。