本文介绍 iOS 进程内存中的五大分区:栈区、堆区、全局区(静态区)、常量区、代码区。涵盖各区的定义、存储内容、地址特征、分配与释放时机,以及栈帧与堆栈溢出等要点,并给出验证示例与对比小结。
目录
- 一、总览
- 二、栈区(Stack)
- 三、堆区(Heap)
- 四、全局区(静态区:.bss 与 .data)
- 五、常量区(.rodata)
- 六、代码区(.text)
- 七、五大区对比与验证
- 八、函数栈与栈帧
- 九、堆栈溢出与预防
- 参考文献
一、总览
在 iOS 中,进程内存主要分为 栈区、堆区、全局区、常量区、代码区 五大区域。各区的地址范围、分配时机与管理者不同,对应不同的使用场景与注意事项。
1.1 五大区速览表
| 分区 | 常见地址前缀(iOS) | 分配时机 | 分配/释放者 | 主要存放 |
|---|---|---|---|---|
| 栈区 | 0x7... | 运行时 | 编译器自动分配与释放 | 局部变量、函数参数、返回地址等 |
| 堆区 | 0x6... | 运行时 | 程序员分配(ARC/MRC 管理释放) | alloc/new 对象、malloc 等动态分配 |
| 全局区 | 0x1... | 编译时 | 程序结束后系统释放 | .bss:未初始化全局/静态变量;.data:已初始化全局/静态变量 |
| 常量区 | 0x1...(只读) | 编译时 | 程序结束后系统释放 | 字符串常量等只读数据(.rodata) |
| 代码区 | — | 编译时 | 程序结束后系统释放 | 程序机器码(.text) |
说明:地址前缀因架构与系统版本可能略有差异,实际以运行打印为准;全局区与常量区在部分环境下可能同属 0x1 段,通过只读/可写与段名区分。
1.2 内存布局示意(由高地址到低地址)
flowchart TB
subgraph 高地址
A[栈区 Stack<br/>0x7... 向下增长]
end
subgraph 中间
B[堆区 Heap<br/>0x6... 向上增长]
end
subgraph 低地址
C[全局区 .bss / .data<br/>0x1...]
D[常量区 .rodata]
E[代码区 .text]
end
A --> B
B --> C
C --> D
D --> E
二、栈区(Stack)
2.1 定义
- 栈是系统级数据结构,与进程/线程一一对应(每个线程有独立栈空间)。
- 栈是向低地址扩展的连续内存区域,遵循先进后出(FILO)。
- 在 iOS 中栈地址通常以 0x7 开头。
- 栈空间在运行时由系统分配。
2.2 存储内容
栈区由编译器自动分配并释放,主要存放:
- 局部变量(含基本类型、指针变量等)。
- 函数参数,包括隐藏参数(如 Objective-C 的
id self、SEL _cmd)。 - 函数调用的返回地址、栈帧信息等(详见 八、函数栈与栈帧)。
2.3 特点与约束
| 项目 | 说明 |
|---|---|
| 优点 | 自动分配释放,无内存碎片,访问高效。 |
| 缺点 | 容量有限,数据生命周期与作用域绑定,不够灵活。 |
| 栈大小 | 主线程栈一般为 1MB,子线程一般为 512KB;macOS 上主线程栈约 8MB。详见 Threading Programming Guide。 |
三、堆区(Heap)
3.1 定义
- 堆是向高地址扩展的不连续内存区域,内部常以链表等结构管理空闲块。
- 在 iOS 中堆地址通常以 0x6 开头。
- 堆空间在运行时由程序动态申请,大小灵活。
3.2 存储内容
堆区由程序员(或 ARC)分配与释放,主要存放:
- Objective-C:
alloc、new、copy等创建的对象。 - C 语言:
malloc、calloc、realloc分配的内存,需配套free释放。
3.3 访问方式
访问堆上对象时,通常先通过栈上的指针(或寄存器)得到对象地址,再通过该地址访问堆内存。即:栈存「指针」,堆存「实际对象数据」。
3.4 特点
| 项目 | 说明 |
|---|---|
| 优点 | 容量大、生命周期可灵活控制,适应面广。 |
| 缺点 | 需管理释放(MRC 手动 / ARC 自动),分配与释放比栈慢,易产生内存碎片。 |
四、全局区(静态区:.bss 与 .data)
4.1 定义
- 全局区在编译时即确定布局,在 iOS 中地址常以 0x1 开头。
- 程序运行期间该区域一直存在,程序结束后由系统释放。
4.2 存储内容
| 段 | 内容 |
|---|---|
| .bss | 未初始化的全局变量、静态变量(系统会将其初始化为 0)。 |
| .data | 已初始化的全局变量、静态变量。 |
全局变量与静态变量:全局变量在全局可见;静态变量由 static 修饰,包括静态局部变量(作用域限于函数内、生命周期贯穿程序)和静态全局变量(本文件内可见)。
五、常量区(.rodata)
5.1 定义
- 常量区在编译时分配,只读,程序结束后由系统释放。
- 对应段名常为 .rodata(read-only data)。
5.2 存储内容
- 字符串常量(如
@"hello"):在程序运行前即分配好,只读,可被多处引用。 - 其他只读常量(如 const 全局、字面量等,视实现而定)。
已在程序中使用的、且无其他指针指向的字符串常量,会在运行前就分配在常量区,以便复用与节省空间。
六、代码区(.text)
6.1 定义与内容
- 代码区在编译时分配,主要存放程序的机器码(指令),对应段一般为 .text。
- 代码被编译成二进制后加载进内存,通常为只读,程序结束后由系统释放。
七、五大区对比与验证
7.1 对比小结表
| 维度 | 栈区 | 堆区 | 全局区 | 常量区 | 代码区 |
|---|---|---|---|---|---|
| 地址特征 | 0x7,向下增长 | 0x6,向上增长 | 0x1 | 0x1(只读) | 低地址 |
| 分配时机 | 运行时 | 运行时 | 编译时 | 编译时 | 编译时 |
| 谁释放 | 编译器/系统 | 程序员/ARC | 系统 | 系统 | 系统 |
| 典型内容 | 局部变量、参数 | 对象、malloc | 全局/静态变量 | 字符串常量等 | 机器码 |
7.2 验证代码示例
通过打印变量与指针的地址,可直观看到各区的分布:
- (void)test {
NSInteger i = 123;
NSLog(@"i 的地址(栈上局部变量): %p", &i); // 预期 0x7...
NSString *string = @"CJL";
NSLog(@"string 对象地址(常量区): %p", string); // 预期 0x1...
NSLog(@"&string 指针地址(栈): %p", &string); // 预期 0x7...
NSObject *obj = [[NSObject alloc] init];
NSLog(@"obj 对象地址(堆): %p", obj); // 预期 0x6...
NSLog(@"&obj 指针地址(栈): %p", &obj); // 预期 0x7...
}
7.3 结果解读
| 打印项 | 含义 | 预期分区 |
|---|---|---|
&i | 局部变量 i 的地址 | 栈区(0x7...) |
string | 字符串对象首地址 | 常量区(0x1...) |
&string | 指针变量 string 的地址 | 栈区(0x7...) |
obj | alloc 创建的对象首地址 | 堆区(0x6...) |
&obj | 指针变量 obj 的地址 | 栈区(0x7...) |
结论:指针变量本身在栈(或寄存器),对象/常量数据在堆或常量区;通过栈上的指针访问堆或常量区。
八、函数栈与栈帧
8.1 概念
- 函数栈即线程使用的栈区,从高地址向低地址增长,与堆区相对。
- 栈帧(Stack Frame):一次未完成调用的函数所占用的一块连续栈空间,用于存放该次调用的参数、局部变量、返回地址等。
8.2 线程与栈帧关系
- 每个线程拥有独立的栈;该线程内所有函数调用共享这份栈空间。
- 每发生一次函数调用,就生成一个栈帧并压栈;函数返回时,对应栈帧出栈并释放。
- 当前线程的栈 = 当前所有未返回函数的栈帧组成的序列。
8.3 ARM 栈帧布局要点
- 栈底在高地址,栈向低地址增长。
- FP(Frame Pointer):栈基址,指向当前函数栈帧的起始位置。
- SP(Stack Pointer):栈顶指针,随压栈/出栈移动。
- 压栈顺序(典型):返回地址、保存的 FP、局部变量、临时变量等;若再调用子函数,会先为子函数参数预留空间。
8.4 栈帧变化示例
int Add(int x, int y) {
int z = 0;
z = x + y;
return z;
}
int main() {
int a = 10;
int b = 20;
int ret = Add(a, b);
return 0;
}
执行过程简述:
- main 入栈:为
a、b等分配栈帧,调用Add前将参数压栈。 - Add 入栈:为
x、y、z等分配新栈帧,SP 下移。 - Add 返回:Add 的栈帧出栈,SP 回到 main 的栈帧,返回值通过约定(寄存器或栈)传回 main。
九、堆栈溢出与预防
9.1 原因简述
- 栈溢出:栈空间有限,递归过深或局部变量过大会导致栈溢出。
- 堆溢出:堆虽大但非无限;过多或过大的动态分配且不释放,会导致堆空间耗尽或 OOM。
9.2 预防要点
| 类型 | 建议 |
|---|---|
| 栈 | 避免层次过深的递归;控制局部变量数量和大小;必要时可考虑改为迭代或增大线程栈(系统 API,谨慎使用)。 |
| 堆 | 避免分配过大的单块对象;及时释放不再使用的对象;注意循环引用与泄漏(ARC 下也要避免强引用环)。 |
延伸阅读
- 本专题索引与总纲:00-主题|内存管理@iOS-索引、02-主题|内存管理@iOS-总纲与知识体系
- 数据在栈/堆中的布局与 内存对齐:08-主题|内存管理@iOS-内存对齐
- 堆上对象的引用计数与 ARC:03-引用计数与MRC详解、04-ARC详解
参考文献
- Threading Programming Guide - Run Loop Management(栈大小等)
- About Memory Management(Objective-C 内存管理)
- 各段名(.text / .data / .bss / .rodata)参见编译与可执行文件格式(如 Mach-O)相关文档。