十四、动态内存管理
1.动态内存分配
动态内存分配指在程序运行时根据需要分配和释放内存的过程,与静态内存分配不同。允许程序更灵活地管理内存资源,适用于不确定数据规模或需要动态调整内存的场景。
2.malloc和free
(一)malloc:分配指定字节数的未初始化内存块。
语法形式:
void* malloc(size_t size);
*若开辟成功,则返回一个指向开辟好空间的指针;若开辟失败,则返回一个NULL指针。
*返回值一定要做检查。
*返回值的类型是void*,malloc函数并不知道开辟空间的类型,具体要使用者自己决定。
*若size为0,具体实现由编译器决定。
*使用时需要包含头文件<stdlib.h>。
(二)free:释放动态分配的内存。
语法形式:
void free(void* ptr);
*如果参数ptr指向的空间不是动态开辟的,则free函数的行为是不可预测的。
*如果参数ptr是NULL指针,则函数什么都不做。
*ptr使用结束后将其赋值NULL。
*使用时需要包含头文件<stdlib.h>。
3.calloc和realloc
(一)calloc:分配并初始化内存为零。
语法形式:
void* calloc(size_t num,size_t size);
*calloc函数的功能是为num个大小为size的元素开辟一块空间,并将每个字节初始化为0;
*与函数malloc的区别就是在返回地址前会初始化。
(二)realloc:调整已分配内存块的大小。
语法形式:
void* realloc(void* ptr,size_t size);
*ptr:指向之前通过malloc,calloc,realloc分配的内存块的指针,如果ptr为NULL,realloc的效果等同于malloc。
*size:新的内存块大小。如果size为0,ptr不是NULL,realloc的效果等同于free。
*realloc尝试调整现有内存块的大小。如果当前内存块之后有足够的连续空间,函数会直接扩展内存块并返回原指针。如果没有足够的空间,realloc会分配一个新的内存块,将原数据复制到新的内存块中,并释放旧的内存块。
*内存分配失败,realloc会返回NULL,但原内存块不会释放。为避免内存泄漏,建议先将返回值赋给一个临时指针比哪里,检查非NULL后在更新原指针。
4.常见的动态内存的错误
(一)对NULL指针的解引用操作
注意对动态开辟内存的指针进行检查,若为空指针则不能使用。
(二)对动态开辟空间的越界访问
访问时注意开辟空间的大小。
(三)对非动态开辟空间使用free释放
free只能释放动态开辟空间。
(四)使用free释放一块动态开辟内存的一部分
释放动态开辟空间时检查指针是否是动态空间的起始位置。
(五)对同一块动态内存多次释放
同一块内存多次释放会引发未定义行为,遵循“谁分配谁释放”原则。(malloc和free成对出现)
(六)动态开辟内存忘记释放(内存泄漏)
释放内存后应将指针置为NULL,动态分配内存后要检查返回值是否是NULL。
*对于malloc/calloc/realloc申请的内存,可以使用free释放。如果没有使用free释放,当程序运行结束时,操作系统也会回收。
5.柔性数组
(一)柔性数组是C99标准引入的一种特殊数组,定义在结构体的最后一个成员位置,且不指定数组长度。
语法形式:
struct example
{
int length;
char data[];//柔性数组
};
*结构中的柔性数组成员前面必须至少有一个其他成员。
*sizeof返回的结构大小不包括柔性数组的内存。
*包含柔性数组的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
(二)柔性数组的优势:结构体和数组内粗连续,提升缓存命中率。仅需一次malloc/free操作,降低内存管理复杂度。
6.总结C/C++中程序内存区域划分
在C/C++中,程序运行时内存通常划分为以下几个主要区域:
(一)内核空间
操作系统内核运行的内存区域。
(二)栈
栈内存由编译器自动分配和释放,存储局部变量,函数参数以及返回地址。速度快但容量有限,栈空间从高地址向低地址增长。
(三)内存映射段
进程虚拟地址空间的一部分,用于将文件或设备直接映射到内存中。
(四)堆
由程序员自己申请,通过malloc、calloc、realloc分配和free释放。堆空间从低地址向高地址动态增长,容量受系统剩余内存限制,分配速度慢但是比较灵活。若程序员不释放,程序结束时可能由OS回收。
(五)数据段(静态区)
存储全局变量、静态变量。程序运行时分配,结束后释放。未初始化的变量会自动初始化为0.
(六)代码段
存储函数体(类成员函数和全局函数)的二进制代码。





