目录
一.为什么要动态内存管理
首先,我们得了解以下几点:
1.变量名称其实是一种外在体现,是给程序员看的,在程序内部指令中都是通过地址进行操作的。
2.每个函数在调用的时候都会分配一块栈空间,作为函数的局部存储,函数内定义的局部变量,使用空间都是在函数栈空间中分配的。
3.一个函数内其实定义的局部变量并不是无限制定义的,如果定义一个超大变量,超过了函数栈空间大小就会报错。
在一个函数中定义一个变量的注意事项:
局部变量:栈空间上内存分配。
1.不能过大,会超出函数栈空间。
2.变量出了作用域空间就会被释放。
3.当保存一组数据时(并不知道数据有多少),只能按照上限个数提前定义好数组,这样就会造成空间的浪费(实际用不了那么多空间)。
eg:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
由此可见:
1. 空间开辟大小是固定的。
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。 这时候就尝试能否进行动态内存开辟(堆上的内存分配)。
二.动态内存开辟
动态内存开辟:在堆区分配内存。
特点:
1.手动申请,也必须手动释放(如果不释放,那么出了作用域也不会自动释放)。
对比:栈上空间的分配:定义了局部变量就会分配空间,出了作用域就会自动释放空间。
注意:申请空间后一定要释放,不释放的话就会造成内存泄漏。
2.需要多少申请多少(实现按需分配)。
3.空间在堆上分配,不占用栈空间(不怕函数栈溢出,一般情况下堆都不会溢出)。
PS:动态地址分配,申请的空间是一整块连续的空间,因此这块空间可以当作数组来用。
三.动态内存函数的操作
1.malloc函数(与free成对出现)
void* malloc (size_t size);
头文件:#include<stdlib.h>
功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
返回值:
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
注意:如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
2.free函数
void free (void* ptr);
头文件:#include<stdlib.h>
功能:free函数用来释放动态开辟的内存,传入的是空间的首地址。
注意:
1. 不要对局部变量进行free。
2.空间释放只能从动态申请的首地址释放,不能从中途释放。
3.释放的是ptr指向的空间,而不是p指针变量本身的空间。
3.calloc函数
void* calloc (size_t num, size_t size);
头文件:#include<stdlib.h>
功能:申请一块空间,并将空间中的数据初始化为0。
参数:num是块个数;size是块大小;num*size就是要申请的空间大小。
返回值:成功返回空间首地址;失败返回NULL。
与函数 malloc 的区别:只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
4.realloc函数
void* realloc (void* ptr, size_t size);
头文件:#include<stdlib.h>
功能:对动态存储的空间在其基础上进行扩容。
参数:ptr是原先的空间首地址;size是要扩容到的空间大小。
返回值:成功有两种情况,第一种是原有空间足够大,那么就在原地址扩容成功,不释放该块空间;第二种是原有空间不够大,则会释放原先的空间,返回新的空间首地址;失败返回NULL。
如果realloc传入的源空间首地址为NULL,那么功能等价于malloc。
5.总结
(1). 野指针
指向了一块不能正常访问的空间。
eg:如果申请了一块内存,将地址赋给了指针变量p,然后对p进行了free,这时候只是把p指向的空间给释放了,并没有修改p变量的指向,这时的p指针就是一个野指针。
(2).动态内存管理中需要注意的问题
- 不要对局部变量的地址进行释放。
- 动态申请的空间不要从中间释放。
- 释放了指针指向的空间后,指针会变为野指针,要注意置为NULL,不要继续访问原空间。
- 动态申请的空间不要重复释放。
- 使用realloc函数扩容成功后,有可能原空间会被释放掉,因此一定要保存新的地址。
- 申请与释放操作一定要成对出现。
四.柔性数组
1.定义
在一个结构体中,最后一个元素是大小未知的数组,数组的空间通过动态内存申请。
可以这样理解:柔性数组不是一个数组,是和结构体搭配使用的一种数据结构。
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
也可以将成员作为指针,独立进行空间的申请与释放,用的时候会麻烦一些,但会更灵活。
typedef struct st_type
{
int i;
int *p;//柔性数组成员
}type_a;
2.特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
3.优点
- 方便内存释放。
- 有利于访问速度。
4.注意
- 柔性数组本身不占空间。
- 柔性数组前边必须有一个成员,柔性数组要放在结构体的最后面。
如有建议或想法,欢迎一起交流讨论~