3.1 OC特性之 記憶體五大地區,oc五大
此篇為針對Objective-c語言入門的基礎知識,為了能讓大家更清楚的理解,此整理中編寫了許多的代碼案例和部分,如有錯誤之處,望指正,願與您相互交流學習,共同進步!---"會飛的猴子_阿新" (同時還要向刀哥致敬)
本篇目標是: 理解記憶體五大地區及各自的職責
目錄結構
- 00.簡述
- 01. 分配和釋放(面試常被問到)
- 02.棧區和堆區
- 2.1 棧區
- 2.1.1 棧區中的儲存(棧區的職責/儲存的內容)
- 2.1.2 棧區的特點
- 2.1.3 其他
- 2.2 堆區
- 2.2.1 堆區中儲存:
- 2.2.2能夠說出堆區的特點
- 0.3 全域變數、靜態變數和常量
- 3.1 全域變數/靜態變數/常量儲存的記憶體地區
- 3.1.1 教科書中 全域變數 和 靜態變數 的儲存地區
- 3.1.2 Xcode 8 中 全域變數 和 靜態變數 的儲存地區
- 注意:(全域/靜態變數,常量)教科書與xcode8驗證有點差別
- (1) xcode8 "全域變數"實際儲存地區案例驗證:
- (2)xcode8 "靜態變數"實際儲存地區案例驗證:
- (3)xcode8 "常量"實際儲存地區案例驗證:
- 3.2 BSS段(靜態區)
- 3.3 資料區段(常量區)
- 3.4 全域變數與全域靜態變數的區別
-
- 注意:
- 1.為什麼OC中幾乎不使用 全域變數?
- 2.不能把 全域變數 定義在標頭檔中
- 3.在程式開發時,全域變數 和 靜態變數 都不能重名
- 4.靜態變數 的修改通常只是在 定義當前靜態變數的檔案 內部進行
- 3.5 靜態變數的正確用法
- 3.6 常量
- 本篇主要學習目標回顧:
- 1.1 記憶記憶體五大地區的名稱
- 1.2 記憶棧區/堆區的職責及特點
- 1.3 記憶全域變數/靜態變數/常量儲存的記憶體地區
- 結束語
00.簡述
程式要想執行,第一步就需要 被載入到記憶體中
記憶體五大地區: 棧區,堆區,BSS段(靜態區),常量區(資料區段),程式碼片段.
01. 分配和釋放(面試常被問到)
02.棧區和堆區
int main(int argc, const char * argv[]) { // 局部變數是儲存在棧區的 // 棧區變數出了範圍之後,就會被銷毀 NSInteger i = 10; NSLog(@"%zd", i); // 局部變數是儲存在棧區的 // 指派陳述式右側,使用 new 方法建立的對象是儲存在堆區的 // xinge 變數中,記錄的是堆區的地址 // 在 OC 中,有一個記憶體管理機制,叫做 `ARC`,可以自動管理 OC 代碼建立對象的生命週期 // 因此,在開發 OC 程式的時候,程式員通常不需要考慮記憶體釋放的工作 LJXPerson *xinge = [LJXPerson new]; NSLog(@"%@", xinge); return 0;}
10
<LJXPerson: 0x100404b70>
2.1 棧區
棧區 (stack [stæk]) : 由編譯器自動分配釋放
2.1.1 棧區中的儲存(棧區的職責/儲存的內容)
-
局部變數
-
方法實參
(eg:在main函數中,調用方法,方法中的實參)
2.1.2 棧區的特點
-
儲存空間有限
. iphone的棧區大小隻有512k
(預設) ,非常有限
-
連續性
. 棧區的地址是連續的
部分代碼:#import <Foundation/Foundation.h>#import "LJXPerson.h"int main(int argc, const char * argv[]) { @autoreleasepool { NSInteger i = 10; NSLog(@"i 的棧區地址是 %p", &i); NSInteger j = 10; NSLog(@"j 的棧區地址是 %p", &j); // xiaoming 變數中,儲存的是 LJXPerson 對象的記憶體位址(堆區的記憶體位址) LJXPerson *xinge = [LJXPerson new]; NSLog(@"xinge 的棧區地址是 %p", &xinge); double height = 10; NSLog(@"height 的棧區地址是 %p", &height); .......
i 的棧區地址是 0x7fff5fbff768
j 的棧區地址是 0x7fff5fbff760
xinge 的棧區地址是 0x7fff5fbff758
height 的棧區地址是 0x7fff5fbff750
地址分配從大到小
. 棧區地址按照分配的順序,由大到小順序排列
訪問速度快
.
系統管理
. (棧區的記憶體由系統管理)
2.1.3 其他
如果在程式中調用方法,會開啟一個 " 棧幀 ".(這個棧幀可以理解為也是一塊連續的地區)棧幀的地址與之前的局部變數的地址不是連續的棧幀中記錄實參地址,以及方法內部的局部變數方法執行完畢後,棧幀銷毀(彈棧) <<<這樣每次執行完畢後,都彈棧釋放記憶體,這樣就會始終保證棧區佔用的記憶體不會特別大>>so-課外話->我們在開發的時候,如果每個方法都寫的很短,同時每個方法聲明的變數都很少.這樣做一定會節約記憶體
見圖知意
添加 "在一個函數/方法中"
即在一個函數/方法中最多可定義65536,--->因為"每次"執行完"一個"方法之後就會彈棧釋放記憶體
棧的概念: 後進先出/ 先進後出
見圖知意:
NSLog(@"NSInteger 變數佔用的位元組數 %tu", sizeof(NSInteger)); NSLog(@"double 變數佔用的位元組數 %tu", sizeof(double)); NSLog(@"xinge 變數佔用的位元組數 %tu", sizeof(xinge)); // 提示:棧區中,只適合儲存非常小的資料 NSLog(@"在 iPhone 中,最多可以在一個函數/方法中定義 %zd 個局部變數", 512 * 1024 / 8); // 測試方法調用時,實參在棧區的儲存情況 [xinge sumWithNum1:i andNum2:j];
總結: 調用方法時棧區的工作原理 * 開啟棧幀 * 儲存實參 * 儲存局部變數 * 方法完成後彈棧,銷毀棧幀,釋放空間
2.2 堆區
堆區 (heap [hiːp]): 由程式員分配釋放,若程式員不釋放,會出現記憶體流失
2.2.1 堆區中儲存:
- 使用 new 方法建立的對象儲存在堆區
- 被建立對象的所有成員變數儲存在堆區中
堆區的職責是"解決棧區空間有限的問題"* OC 使用`new`方法建立的對象--->{由於 **ARC 管理機制**,OC 程式員通常不需要考慮對象的釋放.} (在 OC 中,有一個記憶體管理機制,叫做 ARC(自動引用計數) 可以自動管理 OC 代碼建立對象的生命週期)* C 語言使用 malloc、calloc、realloc 函數分配的空間,需要使用 free 函數釋放
顧: 在開發 OC 程式的時候,程式員通常不需要考慮記憶體釋放的工作
但是:如果在 OC 的代碼中,如果使用到 C 語言分配空間的函數,則需要考慮釋放記憶體
1. 堆區的大小由系統決定,包括:系統記憶體/磁碟交換空間...2. 系統使用`鏈表`來管理堆區中的記憶體配置情況3. {**程式員只需要負責堆區中記憶體的分配和釋放工作**}
2.2.2能夠說出堆區的特點0.3 全域變數、靜態變數和常量3.1 全域變數/靜態變數/常量儲存的記憶體地區
開發要讓 變化控制在有限的範圍內
3.1.1 教科書中 全域變數 和 靜態變數 的儲存地區3.1.2 Xcode 8 中 全域變數 和 靜態變數 的儲存地區注意:(全域/靜態變數,常量)教科書與xcode8驗證有點差別
(1) xcode8 "全域變數"實際儲存地區案例驗證:
案例代碼:
#import <Foundation/Foundation.h>NSInteger num1 = 10; //定義第一個全域變數 並且初始化NSInteger num2; //定義第二個全域變數 沒有初始化int main(int argc, const char * argv[]) { @autoreleasepool { // 提示:在 Xcode 8 中,全域變數無論是否初始化,地址保持不變 NSLog(@"第1個全域變數的地址%p", &num1); //第1個全域變數的地址0x100001188 NSLog(@"第2個全域變數的地址%p", &num2); //第2個全域變數的地址0x100001190 num2=100; NSLog(@"第2個全域變數的地址%p(初始化後的)", &num2); //第2個全域變數的地址0x100001190(初始化後的) } return 0;}
第1個全域變數的地址0x100001188
第2個全域變數的地址0x100001190
第2個全域變數的地址0x100001190(初始化後的)
Program ended with exit code: 0
可見1:地址從小到大是連續的
可見2:
在 Xcode 8 中,全域變數無論是否初始化,地址保持不變
(2)xcode8 "靜態變數"實際儲存地區案例驗證:
#import <Foundation/Foundation.h>static NSInteger num1 = 10; //定義第一個全域變數 並且初始化static NSInteger num2; //定義第二個全域變數 沒有初始化static NSInteger sNum1 = 10; //定義第一個靜態全域變數 並且初始化static NSInteger sNum2; //定義第二個靜態全域變數 沒有初始化int main(int argc, const char * argv[]) { @autoreleasepool { // 提示:在 Xcode 8 中,全域變數無論是否初始化,地址保持不變 NSLog(@"第1個全域變數的地址%p", &num1); //第1個全域變數的地址0x1000011f8 NSLog(@"第2個全域變數的地址%p", &num2); //第2個全域變數的地址0x100001208 num2=100; NSLog(@"第2個全域變數的地址%p(初始化後的)", &num2);//第2個全域變數的地址0x100001208(初始化後的) NSLog(@"##############################################"); NSLog(@"第1個靜態全域變數的地址%p", &sNum1); //第1個靜態全域變數的地址0x100001200 NSLog(@"第2個靜態全域變數的地址%p", &sNum2); //第2個靜態全域變數的地址0x100001210 sNum2=100; NSLog(@"第2個靜態全域變數的地址%p(初始化後的)", &sNum2);//第2個靜態全域變數的地址0x100001210(初始化後的) } return 0; }
第1個全域變數的地址0x1000011f8
第2個全域變數的地址0x100001208
第2個全域變數的地址0x100001208(初始化後的)
################################
第1個靜態全域變數的地址0x100001200
第2個靜態全域變數的地址0x100001210
第2個靜態全域變數的地址0x100001210(初始化後的)
Program ended with exit code: 0
可見1. 靜態變數的初始化前後地址也是不變的
可見2. 靜態變數和全域變數是交叉儲存的
so-->面試注意:
---->在xcode 8中, 靜態變數和全域變數儲存在同一個地區 ---->在xcode 8中, 靜態變數和全域變數都儲存在BSS段中. 跟我們教課書上是有區別的, so-->當面試中遇到這個問題時,我們可以這樣回答: 在教課書中:BSS段存放的是未被初始化的全域變數和靜態變數, 但是我們通過xcode8 驗證後,他們不管是全域變數 還是靜態變數都儲存在BSS段中.
(3)xcode8 "常量"實際儲存地區案例驗證:
#import <Foundation/Foundation.h> NSInteger num1 = 10; NSInteger num2;static NSInteger sNum1 = 10;static NSInteger sNum2;const NSInteger cNum = 10000;//定義一個常量int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"第1個全域變數的地址%p", &num1); NSLog(@"第2個全域變數的地址%p", &num2); num2=100; NSLog(@"第2個全域變數的地址%p(初始化後的)", &num2); NSLog(@"##############################################"); NSLog(@"第1個靜態全域變數的地址%p", &sNum1); NSLog(@"第2個靜態全域變數的地址%p", &sNum2); sNum2=100; NSLog(@"第2個靜態全域變數的地址%p(初始化後的)", &sNum2); //第2個靜態全域變數的地址0x100001230(初始化後的) NSLog(@"##############################################"); NSLog(@"第1個常量的地址%p", &cNum); //第1個常量的地址0x100000e88 } return 0;}
第1個全域變數的地址0x100001218
第2個全域變數的地址0x100001228
第2個全域變數的地址0x100001228(初始化後的)
#################
第1個靜態全域變數的地址0x100001220
第2個靜態全域變數的地址0x100001230
第2個靜態全域變數的地址0x100001230(初始化後的)
##################
第1個常量的地址0x100000e88
Program ended with exit code: 0
常量儲存示範:面試萬一遇到,可示範一下,解釋與科普書上的不同
可見: 全域變數和靜態變數 跟常量放在不同的地區中,常量存放在常量區(或叫資料區段).* 教科書:資料區段(常量區)用來儲存已經初始化的全域變數,靜態變數,常量.* xcode8:中資料區段(常量區)存放的是常量.
3.2 BSS段(靜態區)
儲存的內容: (前面已經案例驗證過了):
1. 教科書: 儲存未初始化
的 全域變數 和 靜態變數
2. Xcode 8驗證: 不管有沒有初始化均儲存 全域變數 和 靜態變數
.
3.3 資料區段(常量區)
儲存的內容: (前面已經案例驗證過了):
3.4 全域變數與全域靜態變數的區別
- 全域靜態變數 通常在定義該變數的檔案內部使用注意:
注意:1.為什麼OC中幾乎不使用 全域變數?
1. 因為程式的任何一個位置都可以對 全域變數 進行修改2. 一旦程式出現由全域變數產生的錯誤,不好排查錯誤3. 使用全域變數不是一個好習慣[我的理解:因為當我們開發時檔案有很多,但是又不能把全域變數定義到標頭檔中(如果定義在標頭檔中,當別的類引入該標頭檔後,相當於在兩個類中都低定義了該全域變數,會出現重複定義),所有幾乎不使用.]
2.不能把 全域變數 定義在標頭檔中
(我的理解:如果定義在標頭檔中,當別的類引入該標頭檔後,相當於在兩個類中都定義了該全域變數,會出現重複定義)
3.在程式開發時,全域變數 和 靜態變數 都不能重名
(我的理解:當跟別的變數重名時,會被重名的變數修改.)
4.靜態變數 的修改通常只是在 定義當前靜態變數的檔案 內部進行
外部即使修改,但是由於地址不同,也不會影響到 定義靜態變數檔案
內部的數值變化
一旦程式因為 靜態變數 產生錯誤,只需要在當前檔案中,檢查一下修改 靜態變數 的代碼即可,排查錯誤難度相對降低
提示:在程式開發時,如果要使用全域的變數,應該在 .m 中定義一個全域的靜態變數,而不要把該變數暴露在標頭檔中-->因為變數範圍越小越可控.
------------------LJXPerson.h---------------------#import <Foundation/Foundation.h>//static NSInteger num1 = 99; //定義一個全域靜態變數@interface LJXPerson : NSObject/** 測試方法 */- (void)test;@end------------------LJXPerson.m---------------------**LJXPerson.m**#import "LJXPerson.h"@implementation LJXPerson- (void)test{ num1--; NSLog(@"%p %zd",&num1,num1);}@end------------------main.m------------------------- #import <Foundation/Foundation.h> #import "LJXPerson.h"int main(int argc, const char * argv[]) { //測試全域的靜態變數 NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1); num1=777777; NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1); LJXPerson *xinge=[LJXPerson new]; [xinge test]; LJXPerson *xiaoming=[LJXPerson new]; [xiaoming test]; NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1); return 0;}
在LJXPerson中定義的全域靜態變數的地址0x100001190,99
在LJXPerson中定義的全域靜態變數的地址0x100001190,777777
0x100001198 98
0x100001198 97
在LJXPerson中定義的全域靜態變數的地址0x100001190,777777
通過:上面的輸出結果也對剛才上面的闡述得到了驗證,以下代碼第6,7,8,13,14行是調用的外部的,即修改的是地址0x100001190對應的變數的值,只有在對象調用方法時是調用的內部的靜態變數.
//測試全域的靜態變數6. NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1);7. num1=777777;8. NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1); 9. LJXPerson *xinge=[LJXPerson new];10. [xinge test]; 11. LJXPerson *xiaoming=[LJXPerson new];12. [xiaoming test];13. NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1);14. return 0;}
main函數中:注釋掉下面的代碼
// NSLog(@"在LJXPerson中定義的全域靜態變數的地址%p,%zd", &num1,num1);// num1=777777;
在LJXPerson中定義的全域靜態變數的地址0x100001190,99
0x100001198 98
0x100001198 97
在LJXPerson中定義的全域靜態變數的地址0x100001190,99
3.5 靜態變數的正確用法
即: static 關鍵字的作用
------------------聲明---------------------#import <Foundation/Foundation.h>@interface LJXPerson : NSObject/** 測試方法 */- (void)test;@end-------------------實現--------------------#import "LJXPerson.h"@implementation LJXPerson- (void)test{ static NSInteger num1 = 99; //定義局部靜態變數 num1--; NSLog(@"%p %zd",&num1,num1);}@end-------------------主程式--------------------#import <Foundation/Foundation.h>#import "LJXPerson.h"int main(int argc, const char * argv[]) { LJXPerson *xinge=[LJXPerson new]; [xinge test]; LJXPerson *xiaoming=[LJXPerson new]; [xiaoming test]; return 0;}
2030-03-31 15:12:48.504 demo[5399:535229] 0x100001170 98
2030-03-31 15:12:48.505 demo[5399:535229] 0x100001170 97
Program ended with exit code: 0
3. 如果當前類檔案中,有 多個方法
使用到該靜態變數,再將該靜態變數修改成全域的
------------------聲明---------------------#import <Foundation/Foundation.h>@interface LJXPerson : NSObject/** 測試方法 */- (void)test;- (void)demo;@end-------------------實現--------------------#import "LJXPerson.h"//當前類檔案中,有 `多個方法` 使用到該靜態變數,放到全域中static NSInteger num1 = 99;@implementation LJXPerson- (void)test{ num1--; NSLog(@"%p %zd",&num1,num1);}- (void)demo{ num1++; NSLog(@"%p %zd",&num1,num1);}@end-------------------主程式--------------------#import <Foundation/Foundation.h>#import "LJXPerson.h"int main(int argc, const char * argv[]) { LJXPerson *xinge=[LJXPerson new]; [xinge test]; [xinge demo]; LJXPerson *xiaoming=[LJXPerson new]; [xiaoming test]; [xiaoming demo]; return 0;}
0x1000011b0 98
0x1000011b0 99
0x1000011b0 98
0x1000011b0 99
3.6 常量
作用:定義一個固定不變
的值,全域統一使用,eg:公司網址,電話等
extern的作用: extern關鍵字,表示該常量的數值,是在其他檔案中設定的,外部可以直接使用此常量.
注意:在一個項目中,常量不能重名,因此在定義常量時,應該盡量長,有首碼,以保證不會出現重名的情況
本篇主要學習目標回顧:
記憶體五大地區
程式要執行,首先需要被載入到記憶體
1.1 記憶記憶體五大地區的名稱
能夠說出記憶體五大地區的名稱
棧區
堆區
BSS段(靜態區)
資料區段(常量區)
程式碼片段
1.2 記憶棧區/堆區的職責及特點
能夠說出棧區職責(儲存的內容)
能夠說出棧區的特點
512K
連續
從大到小
速度快
系統管理
能夠說出調用方法時棧區的工作原理
開啟棧幀
儲存實參
儲存局部變數
方法完成後彈棧,銷毀棧幀,釋放空間
能夠說出堆區的職責(儲存的內容)
- OC 使用 new 方法建立的對象由於 ARC 管理機制,OC 程式員通常不需要考慮對象的釋放
- C 語言使用 malloc 函數分配的空間,需要使用 free 函數釋放
能夠說出堆區的特點
所有程式共用
儲存大資料
程式員管理
不連續
速度沒有棧區快
1.3 記憶全域變數/靜態變數/常量儲存的記憶體地區
開發要讓 變化控制在有限的範圍 內
能夠說出教科書中 全域變數 和 靜態變數 的儲存地區
- 有初始值 的 全域變數 和 靜態變數 儲存在 資料區段(常量區)
- 沒有初始值 的 全域變數 和 靜態變數 儲存在 BSS 段(靜態區)當給 全域變數 或 靜態變數 設定初始值後,會被移動到 資料區段(常量區)
能夠說出 Xcode 8 中 全域變數 和 靜態變數 的儲存地區
- 無論是否設定初始值,全域變數 和 靜態變數 都儲存在 BSS 段(靜態區)能夠說出 常量 的儲存地區常量 儲存在 資料區段(常量區)能夠說出為什麼幾乎不使用 全域變數開發要讓 變化控制在有限的範圍 內不能把 全域變數 定義在標頭檔中,否則會出現重複定義## 1.4 記憶靜態變數/常量的用法
能夠說出 static 關鍵字的作用
能夠說出靜態變數的正確用法
* 如果只有一個方法使用,將 靜態變數 定義在方法內部
* 如果有多個方法使用,將 靜態變數 定義在 .m 中
* 不要把靜態變數定義在標頭檔中
能夠說出常量的作用
定義一個固定不變的值,全域統一使用,例如公司的網址/電話等...
在 iOS 開發中,通常僅僅在定義 通知字串 時才會使用到常量
能夠說出常量的正確用法
在 .m 中定義常量並且設定初始值
const NSInteger cNum = 99;
在 .h 中使用 extern 關鍵字聲明常量在其他位置定義並且已經賦值,外部可以直接使用
extern const NSInteger cNum;
常量名應該盡量的長以避免出現重名
結束語
ok! 以上關於記憶體五大地區基礎知識對您起到學習作用了嗎?如果有錯誤之處望指正!謝謝!相關學習共同進步!真心希望其中某個知識點能幫到您!
以上內容目錄結構 (點擊可調轉)
- 00.簡述
- 01. 分配和釋放(面試常被問到)
- 02.棧區和堆區
- 2.1 棧區
- 2.1.1 棧區中的儲存(棧區的職責/儲存的內容)
- 2.1.2 棧區的特點
- 2.1.3 其他
- 2.2 堆區
- 2.2.1 堆區中儲存:
- 2.2.2能夠說出堆區的特點
- 0.3 全域變數、靜態變數和常量
- 3.1 全域變數/靜態變數/常量儲存的記憶體地區
- 3.1.1 教科書中 全域變數 和 靜態變數 的儲存地區
- 3.1.2 Xcode 8 中 全域變數 和 靜態變數 的儲存地區
- 注意:(全域/靜態變數,常量)教科書與xcode8驗證有點差別
- (1) xcode8 "全域變數"實際儲存地區案例驗證:
- (2)xcode8 "靜態變數"實際儲存地區案例驗證:
- (3)xcode8 "常量"實際儲存地區案例驗證:
- 3.2 BSS段(靜態區)
- 3.3 資料區段(常量區)
- 3.4 全域變數與全域靜態變數的區別
-
- 注意:
- 1.為什麼OC中幾乎不使用 全域變數?
- 2.不能把 全域變數 定義在標頭檔中
- 3.在程式開發時,全域變數 和 靜態變數 都不能重名
- 4.靜態變數 的修改通常只是在 定義當前靜態變數的檔案 內部進行
- 3.5 靜態變數的正確用法
- 3.6 常量
- 本篇主要學習目標回顧:
- 1.1 記憶記憶體五大地區的名稱
- 1.2 記憶棧區/堆區的職責及特點
- 1.3 記憶全域變數/靜態變數/常量儲存的記憶體地區
- 結束語