1.为什么要有动态内存分配
我们已经掌握的内存开辟方式有:

但是上述的开辟空间的方式有两个特点:
· 空间开辟大小是固定的
· 数组空间一旦确定了大小不能调整
但是有时候我们需要的空间大小在程序运行的时候才能知道,那么上述两种方式便不能满足我们的需求了。
而动态内存开辟让我们可以自己申请和释放空间,就比较灵活了
2.malloc和free
malloc和free都声明在stdlib.h头文件中。
2.1malloc
函数原型:void* malloc(size_t size);
功能:向内存的堆区申请一块连续可用的空间,并且返回这块空间的起始地址。
参数:
size :要分配的内存块的字节数
返回值:
· 如果开辟成功,则返回这块空间的起始地址。
· 开辟失败,返回NULL,因此malloc的返回值一定要做检查。
· 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。
· 如果参数size为0,malloc的行为标准是未定义的,取决于编译器。

2.2free
函数原型:void* free(void*ptr)
功能:释放之前通过动态内存分配函数申请的空间
参数:
ptr:指向要释放的内存块的指针
· 如果参数ptr指向的内存不是动态开辟的,那free函数的行为是未定义的
· 如果ptr是NULL指针,则函数什么事都不做
但是注意:
free只是把使用权限还给操作系统,但p中还保存着这个地址,所以必须要置空,避免后续变成野指针。

3.calloc和realloc
3.1calloc
函数原型:void* calloc(size_t num,size_t size)
功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
与malloc的区别:calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
3.2realloc
函数原型:void* realloc(void* ptr,size_t size)
功能:可以调整动态开辟内存的大小。
参数:
ptr:要调整的内存地址
size :调整之后的新大小,单位是字节
返回值:
· 成功:返回一个重新分配内存块的void*类型的指针,这个指针可能跟原来的指针不同。
· 失败:返回NULL,并且原来的内存块保持不变
注意:
· 返回值为调整之后的内存起始位置
· 这个函数会在调整原内存空间大小的基础上,将原来内存中的数据移动到新的空间。
· realloc在调整内存空间时存在两种情况:
情况1:原有空间之后有足够大的空间,最终返回的地址还是旧地址
情况2:原有空间之后没有足够大的空间,会在内存堆区寻找新的满足要求的空间,返回新空间的起始地址,在这个过程中会发生:
· 寻找新的满足要求的空间
· 将旧空间的数据拷贝到新空间,保证数据不会丢失
· 释放旧空间,返回新空间的起始地址
情况3:分配失败,返回NULL。
所以注意下面的写法中用p去接收realloc的返回值就是有问题的,万一分配失败,p被置空,前面二十个字节的数据也就丢失了

4.常见的动态内存的错误
4.1对NULL指针的解引用操作

不做判断直接使用,malloc如果返回失败,那么就会出现对NULL的解引用操作。
4.2对动态开辟空间的越界访问
动态开辟的空间,也是有自己的内存边界的,不能越界访问。
4.3对非动态开辟的内存使用free释放
如下图,如果错误的对arr这个静态内存释放会报错


4.4使用free释放一块动态开辟内存的一部分

上述代码中的p++会使p不再指向动态内存的起始位置,所以最后的free只是释放了一部分的p,运行时也会报错。




