01-主题|内存管理@iOS-内存五大分区

本文介绍 iOS 进程内存中的五大分区:栈区、堆区、全局区(静态区)、常量区、代码区。涵盖各区的定义、存储内容、地址特征、分配与释放时机,以及栈帧与堆栈溢出等要点,并给出验证示例与对比小结。


目录


一、总览

在 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 验证代码示例

通过打印变量与指针的地址,可直观看到各区的分布:

objc 复制代码
- (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 栈帧变化示例

objc 复制代码
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;
}

执行过程简述:

  1. main 入栈:为 a、b 等分配栈帧,调用 Add 前将参数压栈。
  2. Add 入栈:为 x、y、z 等分配新栈帧,SP 下移。
  3. Add 返回:Add 的栈帧出栈,SP 回到 main 的栈帧,返回值通过约定(寄存器或栈)传回 main。

九、堆栈溢出与预防

9.1 原因简述

  • 栈溢出 :栈空间有限,递归过深 或局部变量过大会导致栈溢出。
  • 堆溢出 :堆虽大但非无限;过多或过大的动态分配且不释放,会导致堆空间耗尽或 OOM。

9.2 预防要点

类型 建议
栈 避免层次过深的递归;控制局部变量数量和大小;必要时可考虑改为迭代或增大线程栈(系统 API,谨慎使用)。
堆 避免分配过大的单块对象;及时释放不再使用的对象;注意循环引用与泄漏(ARC 下也要避免强引用环)。

延伸阅读


参考文献

相关推荐
陈随易9 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
kyriewen9 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易9 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员
Hilaku11 小时前
MCP 协议会成为前端 AI 工程的标准接口吗?
前端·javascript·程序员
陈随易18 小时前
Cloudflare 出钱,Vite 团队挑人,44 个维护者分 30 万美元
前端·后端·程序员
SimonKing18 小时前
震惊:怎么回事,opencode一轮对话耗尽免费额度
java·后端·程序员
codigger19 小时前
程序员转型抉择:转 Agent 开发还是转全栈?一份基于招聘市场的冷静分析
程序员·编程·#人工智能·#agent
挖掘狂人19 小时前
程序员转型抉择:转 Agent 开发还是转全栈?一份基于招聘市场的冷静分析
人工智能·程序员·agent
深蓝AI20 小时前
Meta FAIR 开源 e-means:k-means 十分之一的轮数跑出更低损失,已并入 Faiss
人工智能·程序员
爱敲代码的小黄1 天前
神经网络入门指南:从 CNN、RNN 到 Attention
人工智能·面试·程序员