【iOS】内存五大分区

文章目录

前言

iOS里内存主要分为五大区域:栈区、堆区、全局区/静态区、常量区、代码区

五大分区描述的是进程虚拟地址空间的布局,并不是物理内存布局。真正访问时,由MMU完成虚拟地址到物理地址的映射

具体如图:

从下到上分别是从低地址到高地址,代码区→常量区→全局 / 静态区→堆区→栈区,内核区位于最高地址

  • 堆区向上生长,栈区向下生长,这里为啥这么设计是因为这样相向设计,只有到几乎用满整个内存的时候才会相撞,但是同方向几乎很快,一增长就冲突,这样相向提高了内存利用率还可以延迟冲突发生时间

栈为什么一定向下增长?

栈向低地址增长主要是因为早期 CPU 的设计使栈指针递减更高效,同时配合内存高地址布局可以与堆形成相向增长,从而最大化利用内存空间。

为什么堆向上增长?malloc不断申请更高地址

一、栈区(Stack)

核心特性

  • 存储方向:从高地址向低地址分配,内存连续
  • 管理方式:系统自动分配/释放(函数结束后立即回收)
  • 典型存储:局部变量、方法参数(如self,_cmd)、函数返回地址
  • 地址特征:iOS中以0x7或0x16开头

代码演示:

objc 复制代码
int a = 10;
NSLog(@"a == %p size == %lu", &a, sizeof(a));
NSLog(@"方法参数self:%p", &self);
NSLog(@"方法参数cmd:%p", &_cmd);

运行结果:

地址分别从0x16f0a3988(self)->0x16f0a3980(cmd)->0x16f0a396c(a)由高到低递减,步长8字节,验证栈的后进先出的特性

优缺点

  • 优点:速度快(系统自动管理),无内存碎片
  • 缺点:空间受限(iOS主线程栈大小1MB,其他线程512KB)

函数帧和栈帧

栈帧

函数运行时在栈区分配的独立连续的内存区域,包含:

  • 局部变量:函数内定义的变量(如int z = x + y😉
  • 调用记录:返回地址(LR)、当前函数指针(PC)、栈基址(FP)、栈指针(SP)等
  • 参数传递:函数参数通过栈帧传递(如Add(a, b)中的ab

函数调用流程

  1. 压栈:调用函数时,系统重新创建新的栈帧,将参数、局部变量、PC/LR等压入栈区

    例:main函数调用Add(a, b)时,先压入a、b,再压入Add函数的栈帧

  2. 执行:函数在栈帧内执行,操作局部变量(计算z = x + y)

  3. 出栈:函数执行完毕,栈帧出栈,释放内存,程序通过LR返回调用处(从Add返回main)

objc 复制代码
- (void)testStackFrame {  
    int a = 10;  
    int b = 20;  
    int sum = [self addWithA:a b:b];  
    NSLog(@"sum = %d", sum);  
}  

- (int)addWithA:(int)a b:(int)b {  
    return a + b;  
}  

栈帧变化

  • testStackFrame调用时,栈帧压入absum等局部变量。
  • 调用addWithA:b:时,新栈帧压入参数ab,计算后通过 LR 返回结果。
  • 函数结束后,两层栈帧依次出栈,释放内存

二、堆区(Heap)

核心特性

  • 存储方向:从低地址向高地址分配,内存不连续(基于链表管理)
  • 管理方式:手动分配/释放(alloc/free等)要避免内存泄漏
  • 典型存储:OC 对象([NSObject new])、动态分配的数据(malloc
  • 地址特征:iOS里以0x6开头

代码演示:

objc 复制代码
NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
NSLog(@"object1 = %@", object1);
NSLog(@"object2 = %@", object2);

我们看到这两地址不是连续的,验证堆非连续分配的特性

对象在堆里,那成员变量在哪里

objc 复制代码
@interface Person : NSObject
{
    int age;
}
@end

Person *p = [Person new];

如上代码的话

  • p(指针变量)在栈(如果是局部变量)。
  • Person 对象本体在堆。
  • age 是对象的一部分,也在堆里,跟着对象一起存储,而不是单独在栈或全局区。

和栈区的对比

纬度 栈区 堆区
分配方式 系统自动(LIFO) 手动分配(链表遍历)
内存的连续性 连续 不连续
生命周期 函数作用域内 手动释放或程序结束
空间大小 受限制(1MB/512KB) 受限于虚拟内存
碎片问题 容易产生

堆栈溢出的风险

  • 栈溢出
    • 原因:递归深度过深(如无限递归)或者局部变量过多,超出栈空间限制(主线程1MB)
    • 后果:程序崩溃,抛出EXC_BAD_ACCESS异常
  • 堆溢出
    • 原因:频繁分配大内存(如循环创建未释放的对象),耗尽虚拟内存
    • 后果:内存不足,系统终止进程

堆内存碎片为什么产生

假设先 alloc 一个 100 字节的对象 A,又 alloc 一个 200 字节的对象 B,然后 A 释放了。此时堆里有一块 100 字节的空洞。下次要 alloc 一个 150 字节的对象,这 100 字节空洞用不了,只能跳到别处分配 → 碎片越来越多。

这里如果后面有小于等于100的还可以放进100内存,只是后面如果比100大,就会一直空出来,后面可能会越空越多

三、全局区/静态区(Global/Static)

内存划分

  • .bss段:存储的是未初始化的全局变量和静态变量,程序启动时自动清0
  • .data段:存储已初始化的全局变量和静态变量

代码演示:

objc 复制代码
int globalVar;          // 未初始化全局变量(.bss)
static int staticVar;   // 未初始化静态变量(.bss)
int initGlobal = 200;    // 已初始化全局变量(.data)
static int initStatic = 211; // 已初始化静态变量(.data)
- (void)test{
	NSLog(@"globalVar = %p", &globalVar);
    NSLog(@"staticVar = %p", &staticVar);
    NSLog(@"initGlobal = %p", &initGlobal);
    NSLog(@"initStatic = %p", &initStatic);
}

我们看到.bss段和.data地址分别都是连续的,都是相差4字节

static静态变量

objc 复制代码
void test() {
    static int count = 0;//在全局/静态区,但是只在函数内可见
    count++;
    NSLog(@"%d",count);
}
//多次调用test,count地址不变,值增加

区别

类型 作用域 初始化时机
全局变量 全程序可见 程序启动时分配
静态全局变量 当前文件可见 程序启动时分配
静态局部变量 函数内可见 第一次调用时分配

四、常量区(Constant)

  • 存储内容:字符串常量(如@"Hello")以及只读数据、const修饰的只读数据(编译器决定)
  • 特性
    • 只读,程序运行时不可以修改
    • 编译时分配,程序结束后由系统释放

五、代码区(Code/Text)

  • 存储内容:编译后的二进制的指令(函数题、方法实现)
  • 特点:
    • 只读,防止程序运行时意外改变代码
    • 共享性:相同程序的多个实例共享代码区(多个app的进程共享系统库代码)
    • 安全性:防止恶意代码注入和篡改,防御缓冲区的溢出攻击

代码区对应Mach-O中的TEXT段,主要包括:_ text 、_cstring等只读内容。

总结

区域 存什么 谁管理 是否连续 生命周期
局部变量、参数 系统 连续 函数结束
OC对象、malloc ARC/MRC、程序员 不连续 引用计数
全局/静态 全局变量、static 系统 连续 程序结束
常量 字符串常量、只读数据 系统 只读 程序结束
代码 函数、方法实现 系统 只读 程序结束
相关推荐
二流小码农4 小时前
鸿蒙开发:以登录案例了解代码架构MVVM
android·ios·harmonyos
GitLqr6 小时前
别在 Flutter 的 main() 里乱锁屏幕方向,小心 iPad 分屏功能被你搞没了
android·flutter·ios
a44931536210 小时前
iPhone 基带故障诊断——无服务/搜索不到网络的芯片级维修实录
网络·macos·ios·电脑·iphone
a44931536211 小时前
MacBook USB-C不充电故障诊断:从PD握手到主板供电链路
c语言·开发语言·macos·电脑
Summer-Bright11 小时前
消费者 AI 变现竞争:从“一家独大“到“iOS vs Android“,谁在为 AI 买单?
android·人工智能·ios·ai·自然语言处理·agi
寒水馨12 小时前
macOS下载、安装scrcpy-v4.1(附安装包scrcpy-macos-aarch64-v4.1.tar.gz)
macos·adb·手机控制·scrcpy·投屏软件·android投屏·屏幕镜像
a44931536213 小时前
iPhone 充电故障的全链路诊断——从 Lightning/USB-C 接口到 Tristar 充电 IC
c语言·macos·ios·电脑·硬件架构·iphone
xiaowang1234shs1 天前
怪兽轻断食技术深度测评:从断食计时引擎到AI识别算法的工程实践解析
数据库·人工智能·算法·macos·机器学习·p2p·visual studio