目录
1.动态内存管理的原因:
int i = 20;
char arr[30]={0};
上述开辟空间的方式有两个特点:
1.大小是已知的固定的。
2.数组空间开辟,必须指定数组长度,一旦空间开辟成功大小不可改变。
上述定义的变量都是大小已知的,但在我们写代码时,大多数情况下我们具体需要的变量的大小是不确定的。
所有C语言引入动态内存开辟使我们写代码对空间的利用更加合理灵活。
2.1free和malloc
1.free:
free是专门用来释放动态内存空间的,原型如下
void free(void*p);
1.如果free的参数指向的空间不是动态开辟的,则free的行为是未定义的,由编译器决定。
2.参数是NULL,则free函数什么也不做。
3.free函数包含在<stdlib.h>头文件中。
2.malloc:
void*malloc(size_t size);
malloc向内存申请一块连续可用的的空间,并且返回一个指向这个空间的指针。函数包含在<stdlib.h>头文件中。
1.如果这块空间开辟成功,返回一个指针
2.如果开辟失败则返回一个空指针。
3.为什么返回void*,因为malloc并不清楚使用者会开辟什么类型的空间,具体类型由使用者自己决定。
如果size为0,也就开辟空间为零,具体malloc函数会做什么是未知的,取决于编译器。
malloc与free的应用:
//malloc,realloc,calloc的使用都要包含头文件<stdlib.h>
#include<stdlib.h>
#include<errno.h> //perror的头文件
int main()
{
int*p=(int*)malloc(10*sizeof(int));//开辟40字节,(int*)强制转化指针类型
if(p==NULL)
{
perror("malloc");//使用perror来打印错误信息
return 1;//如果开辟失败返回空指针,则直接结束程序返回1
}
int i = 0;
for(i=0;i<10;i++)
{
*(p+i)=i;
}
//打印
for(i=0;i<10;i++)
{
printf("%d",*(p+i));
}
free(p);
p=NULL;//将p置为空指针,防止其成为野指针
return 0;
}
2.2realloc:
realloc函数出现使动态内存管理更加灵活,有时回会感觉开辟的空间太小,有时又觉得开辟空间太大,这是realloc函数就起作用了。realloc函数可以调整开辟后的动态空间的大小。
void*realloc(void*ptr,size_t size);
注意:
1.ptr是要调整空间的内存地址
2.size是调整后的大小
3.调整原来大小的基础上,会把新调整后的空间整体移到新的满足大小的空间
由于第三条的原因,realloc开辟空间有两种可能:
一:向后开辟空间时,后面的空间不够用,这时需要另起一个新的足够的空间开辟,返回的也是不同于原来的旧空间的指针。
二:向后开辟空间,后面有足够的空间去开辟,不需要另起一个新的空间,返回的也是指向原来地址的指针。
如图1(情况一):
图2(情况二)
针对以上两种情况,所以在使用realloc后要判断其返回的否为空指针,如果开辟空间失败则返回空指针。
下面是对realloc使用的用例:
2.2calloc:
C语言提供一个函数可以为num个大小为size字节的元素开辟空间,并把每个字节初始化为0。
calloc与malloc的区别为是否会把每个字节初始化为0
用例:
3.经典笔试题分析
一:栈空间地址及泄露问题
请问这个代码是否能够打印hehe
分析:这个代码有三个错误,分别在55行,56行,48行
1.55行GetMemory函数采用传值的方式,无法将malloc开辟的空间返回给str中,
结束后str仍然为空指针。
因为str为一级指针,p也为一级指针,所以p不能接收str,所以str传进值为0
2.56行strcpy直接对str空指针解引用操作会使程序崩溃。
3.GetMemory中开辟的空间在程序中没有释放。导致内存泄漏
知晓错误后如何改进呢,下面是修改后的代码。
将p改为二级指针,第75行不要忘记释放开辟后的内存空间。
二:返回栈空间地址问题
为什么打印的不是hello world?
一步步分析:程序进入Test后,有个指针存放GetMemory的返回指针。
但是函数中的变量是储存在栈区,一旦函数调用结束,函数占用的空间被回收
此时p中存放的字符串空间已被回收,返回的指针指向的地址已经不存在,
此时再用str来接收,str存放的地址不可被读取,str成为野指针,所以打印的是随机值。
那么如何修改这段代码呢,应该可以在char p[]前面加上static修饰,使其变成静态变量,不会随着函数调用结束而结束,它的生命周期随整个程序结束而结束。
三:内存泄漏问题
乍一看这个代码属实没啥问题,但是最后它漏掉一个细节,在Test函数末尾没有释放malloc函数开辟的空间,并且应该在将str置为空指针。
可以在113行加上free(str);和str=NULL;最好在110行判断GetMemory返回的是不是空指针。
这样一来就没什么问题了 。
四:柔性数组:
柔性数组这个概念可能平常听到的比较少,但它确实存在。在C99中允许存在最后一个成员数组大小是未知的的结构体。这个数组就叫柔性数组。
struct st
{
int i;
char arr[];
};
特点:
1.柔性数组前面至少有一个其他成员变量。比如上面代码的int i;
2.sizeof计算的大小不包含柔性数组的大小。
3.对包含柔性数组的结构体用malloc函数开辟空间,并且开辟的空间应该大于结构体大小,多出空间以便分配给结构体。
柔性数组开辟空间:
这里对柔性数组开辟100个字节空间并赋值打印。
这样柔性数组成员arr获得了连续的空间。
还有另外一种开辟空间的方式:分别开辟结构体与柔性数组的大小
方式一和方式二都可以完成对柔性数组空间的开辟,但哪一种更好一点呢?
方式一更好一点,
1.如果方式二这个代码给别人使用,别人可能不知道除了给结构体释放空间外,还要给结构体成员变量再释放一次,很有可能造成内存泄漏
2.方式一连续开辟的空间有利提高访问速度,减少碎片空间。