UI學習
- iOS-底層原理 24:內存五大區
- 總覽
- 一、棧區(Stack)
- 1.1 核心特性
- 1.2 優缺點
- 1.3函數棧與棧幀
- 1.3 堆棧溢出風險
- 二、堆區(Heap);
- 2.1 核心特性
- 2.2 與棧區對比
- 三、全局 / 靜態區(Global/Static)
- 3.1 內存劃分
- 3.2 關鍵區別
- 四、常量區(Constant)
- 五、代碼區(Code/Text)
- 面試問題學習;
- 問題 1:`static`關鍵字的作用
- 問題 2:堆內存碎片如何產生?
- 問題3:請簡述iOS應用程序的五大內存分區及其主要用途。
- 問題4:為什么棧內存的分配和釋放速度比堆內存快?
- 問題5:全局區和靜態區的內存是如何管理的?它們之間有什么區別? 有問題
- 總結:內存分區速查表
iOS-底層原理 24:內存五大區
總覽
iOS 中,內存主要分為五大區域:棧區、堆區、全局區 / 靜態區、常量區和代碼區。內存布局總覽如下:
由低地址到高地址依次為 代碼區→常量區→全局 / 靜態區→堆區→棧區,內核區位于最高地址。
一、棧區(Stack)
1.1 核心特性
-
存儲方向:從高地址向低地址分配,內存連續。
-
管理方式:系統自動分配 / 釋放(函數結束后立即回收)。
-
典型存儲:局部變量、方法參數(如
self
、_cmd
)、函數返回地址。 -
地址特征:iOS 中以
0X7
或0X16
開頭。
示例代碼:
- (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
)。
- (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
開頭。
示例代碼:
- (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 段:存儲已初始化的全局變量和靜態變量。
示例代碼:
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 對象、動態數據 |
全局 / 靜態區 | 固定 | 可讀可寫 | 編譯時分配 | 全局變量、靜態變量 |
常量區 | 固定 | 只讀 | 編譯時分配 | 字符串、數值常量 |
代碼區 | 固定 | 只讀 | 編譯時分配 | 可執行指令 |