C 语言-内存管理
📑 文章目录
- 一、C 程序内存两大分配体系划分
- 二、程序整体内存四大分区(程序加载进内存后固定布局)
- 三、栈内存运行核心规则
- 四、堆内存:动态内存接口与底层逻辑
- 五、高频内存错误产生逻辑(由内存分区特性直接决定)
- 六、各区域生命周期汇总
- 七、文章补充说明
本文结合 C 语言标准规范,针对单片机、Linux 应用层 C 开发人员,拆解程序运行时五大内存分区基础逻辑,梳理堆、栈、全局区的分配释放规则、函数调用栈运作机制、malloc/free 底层行为,以及内存错误高发场景成因。
一、C 程序内存两大分配体系划分
编译期静态内存分配:编译器在链接阶段就划定内存空间大小,程序运行期间地址、容量固定,无需手动释放。包含只读常量区、全局数据区、静态变量区。
运行期动态内存分配:程序执行过程中主动向操作系统申请内存,由开发者手动管控生命周期,也就是堆内存。搭配函数自动使用的栈内存,构成程序动态运行的内存载体。
二、程序整体内存四大分区(程序加载进内存后固定布局)
程序被操作系统载入内存后,整体分为 4 个区域,从上至下地址分布依次为:
代码段:存放编译后的二进制机器指令、函数实体代码。权限只读,运行中不可修改,字符串常量也寄存于此,程序全程不会被释放。
全局区:放全局变量,静态变量
data 段:初始化的全局变量、初始化 static 静态变量,程序启动时分配,进程销毁才回收;
bss 段:未初始化全局变量、未初始化 static 变量,系统默认填充 0,只占用编译符号表空间,运行时一次性清零分配。
栈区 :系统自动管理内存
函数调用专属内存空间,由 CPU 栈指针寄存器自动分配、函数执行结束自动回收。存放局部普通变量、函数形参、函数返回地址、临时寄存器数据、函数调用现场。栈大小由系统预先设置,容量固定。
堆区:开发者手动管理内存
位于 bss 段与栈中间,地址向上增长。依靠 malloc、calloc、realloc 申请内存,free 主动释放。堆没有固定大小,受系统可用物理内存限制,生命周期完全由代码控制。
三、栈内存运行核心规则
1、栈帧运作机制
每一次函数调用都会生成独立栈帧,函数入栈开辟空间,函数执行完毕栈帧自动出栈销毁,局部变量随之失效。
2、栈核心特性
分配释放自动完成,无需人为操作,执行效率极高;
空间容量受限,递归深度过大、局部数组过长极易造成栈溢出;
栈内存数据随机脏值,局部变量不初始化会携带随机数据。
3、典型使用场景
函数内部临时变量、数组、结构体临时数据、函数参数传递、函数跳转现场保存。
四、堆内存:动态内存接口与底层逻辑
1、四个核心 API 功能
malloc (n):申请 n 字节原始内存,内存保留原有脏数据,不会清零;
calloc (num, size):分配 num 个 size 大小内存块,分配完成自动清零整块内存;
realloc (ptr, new_size):扩容 / 缩容已有堆内存,大概率会更换内存物理地址;
free (ptr):释放已申请的堆内存,释放后指针变为野指针。
(使用时需包含 mclloc.h / stdlib.h 头文件)
2、堆内存分配与释放约束
申请和释放必须一一对应,单次 malloc 仅允许执行一次 free,重复 free 直接引发程序崩溃;
(为了解决这个问题通常会将NULL赋值到已释放内存的指针来防止程序崩溃)
free 只回收内存使用权交还给操作系统,不会清空原有内存数据;
堆内存地址由操作系统分配,地址不连续,存在内存碎片问题。
五、高频内存错误产生逻辑(由内存分区特性直接决定)
栈溢出:局部超大数组、无限递归,超出系统栈预设容量,程序异常崩溃;
野指针 / 悬空指针:栈局部变量地址返回上层函数、堆内存 free 之后继续使用原指针;
内存泄漏:malloc 申请堆内存,程序退出前没有执行 free,长时间运行导致内存占用持续走高;
数组越界访问:读写超出数组开辟的内存范围,篡改相邻内存数据,出现随机性 bug;
修改只读区数据:修改字符串字面量、const 全局常量,触发操作系统内存保护报错。
六、各区域生命周期汇总
代码段、只读段、全局静态区:进程运行全程有效,进程结束由系统统一回收;
栈内存:随函数调用创建,函数返回立刻自动销毁;
堆内存:从 malloc 申请开始生效,调用 free 主动释放,进程退出系统强制回收所有堆内存。
七、文章补充说明
本篇为 C 语言内存管理基础篇章,只讲解分区划分、基础特性、接口规则。