UI学习
- [iOS-底层原理 24:内存五大区](#iOS-底层原理 24:内存五大区)
-
-
- 一、栈区(Stack)
-
- [1.1 核心特性](#1.1 核心特性)
- [1.2 优缺点](#1.2 优缺点)
- 1.3函数栈与栈帧
- [1.3 堆栈溢出风险](#1.3 堆栈溢出风险)
- 二、堆区(Heap);
-
- [2.1 核心特性](#2.1 核心特性)
- [2.2 与栈区对比](#2.2 与栈区对比)
- [三、全局 / 静态区(Global/Static)](#三、全局 / 静态区(Global/Static))
-
- [3.1 内存划分](#3.1 内存划分)
- [3.2 关键区别](#3.2 关键区别)
- 四、常量区(Constant)
- 五、代码区(Code/Text)
- 面试问题学习;
-
- [问题 1:`static`关键字的作用](#问题 1:
static
关键字的作用) - [问题 2:堆内存碎片如何产生?](#问题 2:堆内存碎片如何产生?)
- 问题3:请简述iOS应用程序的五大内存分区及其主要用途。
- 问题4:为什么栈内存的分配和释放速度比堆内存快?
- [问题5:全局区和静态区的内存是如何管理的?它们之间有什么区别? 有问题](#问题5:全局区和静态区的内存是如何管理的?它们之间有什么区别? 有问题)
- [问题 1:`static`关键字的作用](#问题 1:
- 总结:内存分区速查表
-
iOS-底层原理 24:内存五大区
总览
iOS 中,内存主要分为五大区域:栈区、堆区、全局区 / 静态区、常量区和代码区。内存布局总览如下:

由低地址到高地址依次为 代码区→常量区→全局 / 静态区→堆区→栈区,内核区位于最高地址。
一、栈区(Stack)
1.1 核心特性
-
存储方向:从高地址向低地址分配,内存连续。
-
管理方式:系统自动分配 / 释放(函数结束后立即回收)。
-
典型存储 :局部变量、方法参数(如
self
、_cmd
)、函数返回地址。 -
地址特征 :iOS 中以
0X7
或0X16
开头。
示例代码:
objectivec
- (void)testStack {
int a = 10;
NSLog(@"a == %p size == %lu", &a, sizeof(a));
NSLog(@"方法参数 self:%p", &self);
NSLog(@"方法参数 cmd:%p", &_cmd);
}
输出结果:

地址从0x16fdff2a8
(self)→0x16fdff2a0
(cmd)→0x16fdff29c
(a),由高到低递减,步长 8 字节,验证栈的「后进先出」特性。
1.2 优缺点
-
优点:速度快(系统自动管理)、无内存碎片。
-
缺点:空间受限(iOS 主线程栈大小 1MB,其他线程 512KB)。
1.3函数栈与栈帧
栈帧定义 :
函数运行时在栈区分配的独立连续内存区域,包含:
- 局部变量 :函数内定义的变量(如
int z = x + y;
)。 - 调用记录:返回地址(LR)、当前函数指针(PC)、栈基址(FP)、栈指针(SP)等。
- 参数传递 :函数参数通过栈帧传递(如
Add(a, b)
中的a
和b
)。
函数调用流程:
-
压栈
:调用函数时,系统创建新栈帧,将参数、局部变量、PC/LR 等压入栈区。
- 例:
main
函数调用Add(a, b)
时,先压入a
、b
,再压入Add
函数的栈帧。
- 例:
-
执行 :函数在栈帧内执行,操作局部变量(如计算
z = x + y
)。 -
出栈 :函数执行完毕,栈帧出栈,释放内存,程序通过 LR 返回调用处(如从
Add
返回main
)。
objectivec
- (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
调用时,栈帧压入a
、b
、sum
等局部变量。- 调用
addWithA:b:
时,新栈帧压入参数a
、b
,计算后通过 LR 返回结果。 - 函数结束后,两层栈帧依次出栈,释放内存。
1.3 堆栈溢出风险
- 栈溢出:
- 原因:递归深度过深(如无限递归)或局部变量过多,超过栈空间限制(如主线程 1MB)。
- 后果:程序崩溃,抛出
EXC_BAD_ACCESS
异常。
- 堆溢出:
- 原因:频繁分配大内存(如循环创建未释放的对象),耗尽虚拟内存。
- 后果:内存不足,系统终止进程。
二、堆区(Heap);
2.1 核心特性
-
存储方向:从低地址向高地址分配,内存不连续(基于链表管理)。
-
管理方式 :手动分配 / 释放(
alloc
/free
等),需避免内存泄漏。 -
典型存储 :OC 对象(
[NSObject new]
)、动态分配的数据(malloc
)。 -
地址特征 :iOS 中以
0x6
开头。
示例代码:
objectivec
- (void)testHeap {
NSObject *object1 = [NSObject new];
NSObject *object2 = [NSObject new];
NSLog(@"object1 = %@", object1);
NSLog(@"object2 = %@", object2);
}
输出结果:

地址0x6000029ac070
与0x6000029ac080
不连续,验证堆的「非连续分配」特性。
2.2 与栈区对比
维度 | 栈区 | 堆区 |
---|---|---|
分配方式 | 系统自动(LIFO) | 手动分配(链表遍历) |
内存连续性 | 连续 | 不连续 |
生命周期 | 函数作用域内 | 手动释放或程序结束 |
空间大小 | 受限(1MB/512KB) | 受限于虚拟内存 |
碎片问题 | 无 | 易产生 |
三、全局 / 静态区(Global/Static)
3.1 内存划分
-
.bss 段 :存储未初始化的全局变量和静态变量,程序启动时自动清零。
-
.data 段 :存储已初始化的全局变量和静态变量。
示例代码:
objectivec
int globalVar; // 未初始化全局变量(.bss)
static int staticVar; // 未初始化静态变量(.bss)
int initGlobal = 10; // 已初始化全局变量(.data)
static int initStatic = 11; // 已初始化静态变量(.data)
- (void)testStatic {
NSLog(@"globalVar = %p", &globalVar);
NSLog(@"staticVar = %p", &staticVar);
NSLog(@"initGlobal = %p", &initGlobal);
NSLog(@"initStatic = %p", &initStatic);
}
输出结果:

分析:
-
.bss段
地址连续(0x100008100
→0x10008104
,相差 4 字节)。 -
.data段
地址连续(0x1000080f8
→0x1000080fc
,相差 4 字节)。
3.2 关键区别
类型 | 作用域 | 初始化时机 |
---|---|---|
全局变量 | 全程序可见 | 程序启动时分配 |
静态全局变量 | 当前文件可见 | 程序启动时分配 |
静态局部变量 | 函数内可见 | 第一次调用时分配 |
四、常量区(Constant)
-
存储内容 :字符串常量(如
@"Hello"
)、基本类型常量(如123
、3.14
)。 -
特性:
- 只读,程序运行时不可修改。
- 编译时分配,程序结束后由系统释放。
五、代码区(Code/Text)
-
存储内容:编译后的二进制指令(函数体、方法实现)。
-
特性:
-
只读,防止程序运行中意外修改代码。
-
共享性:相同程序的多个实例共享代码区(如多个 App 进程共享系统库代码)。
-
安全意义:阻止恶意代码注入和篡改,防御缓冲区溢出攻击。
-
面试问题学习;
问题 1:static
关键字的作用
- 对于全局变量来说,static 改变了其作用域。普通全局变量是所有文件都可以用。静态全局变量是只有当前文件可以用。
- 对于局部变量来说,static改变了其存储方式从而改变了生命周期。普通局部变量是动态存储,动态存储决定了其生命周期为变量使用期间。静态局部变量是静态存储,存储在全局静态区,生命周期为从程序开始到结束。
- 因此 static 这个说明符在不同的地方所起的作用是不同的。
- 总结:全局变量、静态全局变量、静态局部变量采用静态存储方式,局部变量采用动态存储方式。
问题 2:堆内存碎片如何产生?
-
原因:频繁分配 / 释放不同大小的内存块,导致空闲内存碎片化(如分配 100 字节→释放→分配 20 字节→释放,留下 80 字节小块无法被大内存请求利用)。
-
iOS 优化方案:
使用 ARC 自动管理内存,减少手动操作。
对象池技术(如`NSOperationQueue`复用线程)。
避免高频小内存分配(如改用缓存机制)。
问题3:请简述iOS应用程序的五大内存分区及其主要用途。
-
栈(Stack):
- 用途:用于存储局部变量、函数参数、返回地址等。栈内存是自动分配和释放的,主要用于函数调用和局部变量的管理。
- 特点:内存分配方式为LIFO(后进先出),存取速度快,空间相对较小。
-
堆(Heap):
- 用途 :用于动态分配内存,存储需要在运行时分配和释放的对象和数据。堆内存由程序员手动管理,通过
malloc
、free
、new
、delete
等函数进行分配和释放。 - 特点:内存管理灵活,存储空间较大,但分配和释放速度相对较慢,容易产生内存碎片。
- 用途 :用于动态分配内存,存储需要在运行时分配和释放的对象和数据。堆内存由程序员手动管理,通过
-
全局区/静态区(Global/Static):
- 用途:存储全局变量和静态变量。全局变量在程序启动时分配,在程序结束时释放;静态变量在第一次使用时分配,程序结束时释放。
- 特点:内存地址固定,生命周期贯穿程序运行的整个周期。
-
常量区(Constant):
- 用途:存储常量数据,例如字符串常量、数值常量等。常量区的内容在程序运行时不可修改。
- 特点:只读区域,数据在程序加载时初始化,生命周期贯穿程序运行的整个周期。
-
代码区(Code/Text):
- 用途:存储程序的可执行代码,包括函数体和编译后的指令。代码区在程序运行时是只读的,以防止意外修改。
- 特点:只读区域,存储的是编译后的机器指令,生命周期贯穿程序运行的整个周期。
问题4:为什么栈内存的分配和释放速度比堆内存快?
- 分配方式:栈内存采用LIFO(后进先出)的分配方式,每次函数调用时,函数的局部变量、参数和返回地址会依次入栈,函数返回时,这些数据会依次出栈。分配和释放只需要移动栈指针,操作简单且高效。
- 内存管理 :栈内存由系统自动管理,函数调用结束时,系统会自动释放栈内存,无需程序员手动管理。堆内存则需要程序员手动管理,通过
malloc
、free
等函数进行分配和释放,管理复杂且容易产生内存碎片。 - 空间连续:栈内存通常是连续的内存块,分配和释放时不需要进行复杂的内存碎片整理,而堆内存由于频繁的分配和释放,容易产生内存碎片,导致分配和释放速度变慢。
问题5:全局区和静态区的内存是如何管理的?它们之间有什么区别? 有问题
-
全局区(Global):
- 管理全局变量,即在程序的整个生命周期内都存在的变量。这些变量在程序启动时分配内存,在程序结束时释放内存。
- 全局变量在定义时如果未显式初始化,系统会将其初始化为0。
-
静态区(Static):
- 管理静态变量,即在函数或类内部定义并带有
static
关键字的变量。这些变量在第一次使用时分配内存,在程序结束时释放内存。 - 静态变量在第一次定义时如果未显式初始化,系统也会将其初始化为0。
- 管理静态变量,即在函数或类内部定义并带有
区别:
- 生命周期:全局变量和静态变量的生命周期相似,都是在程序运行期间存在,但全局变量在程序启动时即被初始化,而静态变量在第一次使用时才被初始化。
- 作用域:全局变量的作用域是整个程序,而静态变量的作用域仅限于其定义的函数或类内部。
总结:内存分区速查表
分区 | 地址方向 | 读写权限 | 管理方式 | 典型存储 |
---|---|---|---|---|
栈区 | 高→低 | 可读可写 | 系统自动 | 局部变量、函数参数 |
堆区 | 低→高 | 可读可写 | 手动分配 | OC 对象、动态数据 |
全局 / 静态区 | 固定 | 可读可写 | 编译时分配 | 全局变量、静态变量 |
常量区 | 固定 | 只读 | 编译时分配 | 字符串、数值常量 |
代码区 | 固定 | 只读 | 编译时分配 | 可执行指令 |