文章目录
头文件引用
“malloc.h”
内存开辟函数
void *malloc(size_t size);
开辟 size 个字节的连续内存空间,未进行初始化(前提:内存池有满足需求的内存大小)
void* calloc (size_t num, size_t size);
开辟 num 个 size 字节的连续内存空间,并初始化为0(前提:内存池有满足需求的内存大小)
void *realloc(void *ptr, size_t new_size)
调整 malloc / calloc 分配的内存块(ptr)大小为 new_size 个字节
对于前两个函数的说明:
- 若内存池中的可用内存满足需求,则返回被分配空闲内存块的首地址
- 若内存池为空或可用内存无法满足需求,则向操作系统请求得到更多内存
- 若操作系统能够向malloc提供更多内存,返回空闲内存块的首地址
- 若操作系统无法向malloc提供更多内存,返回 NULL
对于第三个函数的说明:
- 若用于扩大一块内存,则保留原来的内容,新内存不进行初始化
- 若原来的内存块能够进行满足需求的延申:直接向后延申内存
- 若原来的内存块无法进行满足需求的延申:开辟满足需求的内存,将原内存内容复制到新内存 - 若用于缩小一块内存,则尾部内容被删除,剩余部分依然保留
- 因扩大内存块可能会造成地址更改,故不建议在对内存块进行调整后使用原地址
对于返回值,你需要注意:
- NULL检测:时刻注意要进行 NULL 检测,确保内存分配成功
- 强制转换:返回的指针是 void * 类型,在使用时最好先进行强制转换
- 边界对齐:返回值始终能够满足边界对齐的最高要求
补充说明:
- 实际分配的内存可能比请求的稍大些,这是由编译器决定的,额外的空间用来记录管理信息(下面会进行描述),在对指针进行操作时,不要试图访问额外空间,否则会造成不必要的错误
- 你可以去尝试申请字节数为0的空间,你会发现返回值并非 NULL ,而是一个有效地址,除了 free 不要试图去使用该地址
- 在传递 size 参数时尽量使用 sizeof 来计算数据类型的长度,提高程序的可移植性
内存释放函数
void free (void* pointer);
释放由 malloc / calloc 申请的内存空间,pointer为该内存块的首地址
关于内存释放,你需要注意:
- 传递给 free 的指针必须是一个从 malloc / calloc / realloc 函数返回的指针,否则可能会出错
- 在所有执行程序共享一个通用内存池的操作系统中,分配的内存在使用完毕后一定要释放,否则将导致内存泄漏;更安全的用法是,不论什么情况下,使用完毕后都应立即释放
- free 只释放了申请的内存,并不改变pointer指针的值
- 因 pointer 所指向的内存块已被释放,故其他代码可能改变这块内存的值,即pointer所指向的地方不受控制 (称为野指针),为避免出错,在调用 free 后,将 pointer 指向 NULL,并注意不要对之前已经传递给其他的变量进行使用,总之,不要访问已被 free 函数释放了的内存
- 内存泄漏:指内存被动态分配后,当它不再使用时未被释放;内存泄漏会增加程序体积,可能导致程序或系统崩溃
关于内存块的补充
- 有些内存分配与释放的实现,若对分配内存进行越界访问,可能暂时不会出现问题,若一旦出现问题,就很难被发现;
- 有些内存分配与释放的实现,是按照链表的形式维护可用内存池的,若进行越界访问,可能就会破坏链表,产生异常而终止程序,
- 链表分为单链表和双链表,单链表包含指向下一个链表的指针,双链表则多一个指向上一个链表的指针
- 空闲内存以空闲链表的方式组织,每个内存块都包含一个空闲内存长度信息和指针信息
- 当进行内存申请时,申请函数会对链表进行扫描,直到找到满足要求的内存块为止,故每次调用同一个内存申请函数时,花费的时间不是固定的
- 若找到的内存块恰好符合大小,则将该部分内存块的空闲内存地址返回,并改变链表信息
- 若找到的内存块太大,则进行分割,只取大小合适的内存块,剩余部分组成新的内存块并入链表
- free 函数在释放时,搜索链表,找到可以插入被释放块的合适位置,若与被释放块相邻的任一边是一个空闲块,则合并为一个更大的块,减少内存碎片
- 更详细的内容,请参考
malloc/free的实现及malloc实际分配/释放的内存
Malloc内存泄露和内存越界问题的研究
malloc内存,使用越界,free出错的问题