目录
一,动态内存的作用
创建变量开辟空间:
int a = 0; //开辟4个字节
char arr[10] = {0}; //开辟10个字节
像这样开辟的内存空间是固定的,是不可调整的。数组变量在创建时需要指定大小,一旦指定之后就不可以被修改。
但是有时候我们需要自己自由创建空间,就引出了动态内存,可以由程序员自己创建空间和释放空间,比较灵活。
补充:动态开辟的函数都放在堆区

二,malloc和free
1,malloc函数
作用:用来动态内存开辟
头文件:#include <stdlib.h>
语法:void* malloc(size_t size);
size为申请空间的大小,单位为字节,类型为无符号整型(size_t)
返回值为void*表示:函数返回一个申请到的空间的地址,void表示不知道是什么类型,需要在使用时由使用者自己决定。
如果开辟成功,则返回一个开辟好空间的指针
如果开辟失败,则返回NULL指针,由此一定要对malloc开辟的返回值进行检查:if判断或者assert(p!=NULL)
特殊情况:如果size的大小为0,则属于标准未定义,编译器进行提示。
运用举例:

2,free函数
核心功能:专门用来释放和回收动态内存函数开辟的空间。
头文件:#inclde <stdlib.h>
语法:void free(void * ptr);
使用细节:每使用一次malloc函数或者calloc函数,就要使用free函数来释放内存。
特殊情况:
1,如果ptr不是动态内存开辟的空间,则free函数的行为是未定义的。
2,如果ptr是NULL指针,则函数什么也不做。

上图表示:先释放掉p中的内容,再将p定义为空指针。
三,calloc和realloc
1,calloc函数
作用:同样用来动态内存分配
头文件:#include <stdlib.h>
语法:void* calloc(size_t num,size_t size);
把num个size大小的元素,开辟成一块空间,并把每个空间的每个字节初始化为0
与malloc的唯一区别为:会把开辟的空间的字节初始化为0
运用实例:

由上图可知:开辟的空间都被初始化为了0
2,realloc函数
作用:让动态内存管理更加灵活
头文件:#include <stdlib.h>
语法:void* realloc(void* ptr,size_t size);
ptr是要调整的内存的起始地址
size是调整后的内存的大小
返回值为调整之后的内存的起始地址
内存中的原有储存的值不变
realloc在调整空间时,所遇到的两种情况:
第一种:原有空间之后有足够大的空间满足调整之后的大小。
第二种:原有空间之后没有足够大的空间满足调整之后的大小,此时会将原来的空间释放,重新开辟一块足够大的新空间,并将原来储存的值拷贝到新空间内,此时起始地址改变,返回的是一个新的地址。
特殊情况:调整失败,返回的是NULL
运用展示:
1,


2,

3,

四,常见动态内存错误分析
1,没有对malloc的返回值进行检查
2,对动态开辟空间的越界访问

3,对于非动态内存开辟使用free释放

free只能用于动态内存开辟的函数,对于非动态内存的函数开辟的空间会报错。
4,对动态内存的一部分使用free

5,动态内存开辟忘记释放(内存泄漏)

注意使用动态开辟函数开辟空间在最后一定要使用free进行释放。
通常满足使用一次malloc / calloc后就要使用一次free(成对出现)
特殊情况:使用了free但没效果。
例如:

此时虽然成对出现了,但是依旧出错,就是由于没有执行到free部分。
五,题目案例分析
1,例题分析1:

分析错误:
1,没有使用free进行内存的释放
2,传入p的是NULL,开辟一个新空间,但p一出函数就销毁,str最终没有得到开辟的空间,依旧是空指针。
修改后:

2,例题分析2:

分析错误:
1,p返回的是数组首元素的地址,但是在GetMemory函数执行完后,p就销毁了,p中的内容就还给了操作系统了,但str依旧得到了地址,里面的内容无法访问,强制使用str访问就会导致出现非法访问。
此时的str是野指针(指向的空间已经被释放)。
3,例题分析3:

六,柔性数组
1,了解柔性数组
当结构体的最后一位成员是一个大小未知的数组,这个结构成员就是柔性数组。
struct S
{
int i;
char k;
int arr[]; //或者int arr[0] //柔性数组
}
2,柔性数组的特点
1,结构体的柔性数组成员前面必须要有其他的成员。
2,使用sizeof计算结构体大小时,柔性数组的大小不计入其中。

3,柔性数组的使用

通过malloc和realloc实现了对柔性数组空间的任意分配,这也是柔性数组柔性的原因。

另一种写法:

比较这两种写法:
第一种写法相比较于第二种写法来说有两种好处:
1,第一种写法由于只使用了一次malloc,所以只需要使用一次free就可以将使用的内存释放掉。
2,第一种写法开辟的空间是连续的,而连续的空间有利于提高访问速度,减少内存碎片(内存中不同空间之间的空隙)。
七,补充内容
1,perror函数
perror是一个错误处理函数,会将当前的代码错误转化为可读的错误信息,并输出。
语法:void perror(const char* s);
s为一个自定义字符串,会在错误信息前输出
输出格式:s:错误信息
展示:

其中INT_MAX为2147483647
此时开辟的空间过大,开辟失败
2,exit函数
核心功能:用于立即终止程序
头文件:#include <stdlib.h>
语法:void exit(int status);
status:程序退出状态码
当为0或EXIT_SUCCESS时:表示程序正常退出
当为非零整数或EXIT_FAILURE时:表示程序异常退出
EXIT_FAILURE是一个标准的宏(通常值为1),它代表"程序异常终止"。
3,程序的内存区域划分

