咱們平時寫的代碼Objective-C,底層實現其實都是C/C++的代碼實現的,高級語言通過編譯器編譯,最終轉化爲機器語言。 因此,咱們的Objective-C的面向對象,其實都是基於C/C++的數據結構實現的。那麼Objective-C的對象、類主要是基於C/C++的什麼數據結構實現的呢?算法
那究竟是什麼樣的數據結構結構?是數組嗎?咱們都知道數組只能存儲同一種類型的數據,而對象會有不一樣的屬性,好比Student
這個類,它有姓名(string
),身高(double
),等等都是不一樣的數據類型,很顯然不是數組的結構類型!那麼很顯然只有一種結構能知足,那就是結構體(struct
)。那究竟是不是呢?咱們來探索一下。數組
咱們創建一個工程,而後編譯成C++
看看markdown
編譯前數據結構
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc]init];
}
return 0;
}
複製代碼
輸入一下面這個命令進行編譯架構
clang -rewrite-objc main.m -o main.cppapp
咱們看到輸出了一個C++
的文件main.cpp
打開編譯後的文件能夠看到,
main
函數變成了底下這個屌樣子iphone
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
複製代碼
不一樣平臺下面的代碼是不同,好比Windows,macOS,iOS
,那麼咱們確定是但願是支持iOS
系統下的C++
代碼,那麼用下面這個命令函數
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 輸出文件學習
意思就是Xcode編譯是跑在arm64架構的iPhone平臺上的,-o是輸出的意思ui
運行以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
編譯完的底層源碼有好幾萬行,在7400行
,咱們能夠看到,NSObject
的底層是結構體
struct NSObject_IMPL {
Class isa;
};
複製代碼
這也就驗證了OC
的底層是結構體,那麼這個Class isa
是個什麼東東呢? 咱們進入裏面看看
typedef struct objc_class *Class;
複製代碼
這個 *Class
就是指向結構體的指針,那麼指針在結構裏面佔多少字節呢?若是是64位就是8個字節,32位就是4個字節,那麼如今的系統都是64位的了,在這個結構體裏面,Class isa
成員變量就是佔8個字節。那麼這個結構體也就是8個字節,由於如今這個結構體裏面沒有其餘的屬性和變量,這個Class isa
成員變量是默認帶上的,因此結構體就是佔8個字節。
下面的這個代碼是實例化出了一對象obj
,其實底層實現就是上面👆講的一個結構體,裏面會有一個 Class isa
成員變量
NSObject *obj = [[NSObject alloc]init];
複製代碼
假如isa
地址是0x12300001
,obj
這個指針的地址是多少呢??? alloc
已經分配了內存,那麼這個指針就是首地址,而裏面只有一個成員變量,那麼isa
的地址就是結構體在內存中的地址,那麼obj=0x12300001
。 結構體的大小是指針的大小,那NSObject
對象在內存中的大小是多少呢?是否是也是8
個字節呢???其實不是的,是16
字節。啊???爲何是16
字節呢???一臉疑惑臉🤔,那咱們接着往下探索👇
咱們能夠打印看看NSObject
實例對象的成員變量所佔用的內存大小 引入頭文件 #import<objc/runtime.h>
#import<objc/runtime.h>
NSLog(@"InstanceSize:%zd",class_getInstanceSize([NSObject class]));
複製代碼
咱們能夠看到輸出的結果是
8
那咱們再看看,obj
所指向的內存的大小 導入頭文件#import<malloc/malloc.h>
NSLog(@"malloc_size:%zd",malloc_size((__bridge const void *)(obj)));
複製代碼
打印結果以下 從打印的結果來看,是
16
,這也就驗證了上面👆說的:NSObject
對象在內存中的大小是16
字節。po
打印一下 也能夠查看地址在內存中的分佈
Debug->Debug Workflow->View Memory
下圖就是內存分配狀況
從上圖很明顯看出來是
16字節
,前8
位是isa
,後8
位就是內存分配預留的8
字節。那爲何要預留呢?明明8
個就夠用了,分配16
幹嗎???內存資源
是很珍貴的啊!CPU
是否是傻啊???帶着這個疑問,咱們繼續往下探索👇
下面的代碼打印結果是多少呢???
@interface Student : NSObject
{
int _age;
int _num;
}
@end
@implementation Student
@end
Student *stu = [[Student alloc]init];
NSLog(@"InstanceSize:%zd",class_getInstanceSize([Student class]));
NSLog(@"malloc_size:%zd",malloc_size((__bridge const void *)(stu)));
NSLog(@"sizeof:%lu",sizeof(stu));
複製代碼
打印結果 經過命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
轉爲cpp文件
是以下結構
struct Student_IMPL {
Class isa;
int _age;
int _num;
};
複製代碼
由於Student
是繼承自NSObjec
t,Student
的結構體裏面有個成員變量isa
是指向父類NSObject
的。isa
是8
個字節,int
類型是4
個字節,一共就是16
字節。那我Student
裏面少一個成員變量呢?那結果是12
仍是16
呢???? 結果是
16
,why???爲何呢?這就是字節對齊
,class_getInstanceSize
是計算類的成員變量的大小,實際上計算的並非嚴格意義上的對象的內存的大小,由於內存進行了8
字節對齊,從objc
的底層源碼能夠看到,核心算法是define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK
。
補充:
sizeof
不是一個函數,是C/C++
中的一個操做符(operator)sizeof()
是一個判斷數據類型或者表達式長度的運算符。
實際上對象的內存對齊是16字節對齊,咱們繼續往下探索👇
struct
)(或聯合(union
))的數據成員,第⼀個數據成員放在offset
爲0的地⽅,之後每一個數據成員存儲的起始位置要 從該成員⼤⼩或者成員的⼦成員⼤⼩(只要該成員有⼦成員,⽐如說是數組, 結構體等)的整數倍開始(⽐如int
爲4
字節,則要從4的整數倍地址開始存 儲。 min
(當前開始的位置m
大小n
) 好比: m = 9 n = 4
--> 9 10 11 12
2. 結構體做爲成員:若是⼀個結構⾥有某些結構體成員,則結構體成員要從 其內部最⼤元素⼤⼩的整數倍地址開始存儲(struct a
⾥存有struct b
,b
⾥有char
,int
,double
等元素,那b應該從8的整數倍開始存儲.) 3. 收尾⼯做:結構體的總⼤⼩,也就是sizeof
的結果,必須是其內部最⼤ 成員的整數倍不⾜的要補⻬。
桂花上代碼,先上幾個結構體嚐嚐味兒,哈哈😁
struct Student1{
double a; // 8 [0 7]
char b; // 1 [8]
int c; // 4 (9 10 11 [12 13 14 15]
short d; // 2 [16 17] 24
} Student1;
struct Student2{
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15] 16
} Student2;
NSLog(@"Student1:%lu-Student2:%lu",sizeof(Student1),sizeof(Student2));
複製代碼
下面就開始細細的品嚐這兩道開胃小菜吧!根據內存對齊原則進行簡單的計算和分析
Student1
內存大小詳細過程min(m,n)
,m
表示當前開始的位置,n
表示大小)
a
: 佔8
個字節,offert從0開始, min(0,8)
, 即0 ~ 7
存放a
b
: 佔1
個字節,offert從8開始, min(8,1)
, 即8 ~ 8
存放b
c
: 佔4
個字節,offert從12開始,min(12,4)
,即12 ~ 15
存放c
,中間九、十、11不是4的倍數,因此得空出來。d
: 佔2
個字節,offert從14開始,min(16,2)
,即16~17
存放d
下面👇放上
Student1
的內存分佈圖,便於理解
根據
對齊原則3
,結構體的總⼤⼩,必須是其內部最⼤
成員的整數倍
,不⾜的要補⻬
,Student1
中最大的是8
,因此最後爲24
。
Student2
內存大小分析
a
: 佔8
個字節,offert從0開始, min(0,8)
, 即0 ~ 7
存放a
b
: 佔4
個字節,offert從8開始, min(8,4)
, 即8 ~ 11
存放b
c
: 佔1
個字節,offert從12開始,min(12,1)
,即12 ~ 12
存放c
d
: 佔2
個字節,offert從14開始,min(14,2)
,即14~15
存放d
下面👇放上
Student2
的內存分佈圖,便於理解
爲何
d
的存放不從13
開始,由於13
不是2整數倍
,因此從14
開始,根據對齊原則,最後爲16
。
開胃菜吃完了,那就再來道硬菜,7788。 桂花上菜!!!!!
struct Student3 {
double a; // 8 [0 7]
int b; // 4 [8 9 10 11]
char c; // 1 [12]
short d; // 2 (13 [14 15]
int e; // 4 [16 17 18 19]
struct Student1 str;//(20 21 22 23 [24 ~ 47]
}Student3;
NSLog(@"Student1:%lu-Student2:%lu-Student3:%lu",sizeof(Student1),sizeof(Student2),sizeof(Student3));
複製代碼
打印結果以下
Student3
內存大小分析
a
: 佔8
個字節,offert從0開始, min(0,8)
, 即0 ~ 7
存放a
b
: 佔4
個字節,offert從8開始, min(8,4)
, 即8 ~ 11
存放b
c
: 佔1
個字節,offert從12開始,min(12,1)
,即12 ~ 12
存放c
d
: 佔2
個字節,offert從14開始,min(14,2)
,即14~15
存放d
e
: 佔4
個字節,offert從16開始,min(16,4)
,即16~19
存放e
這道Student3
菜確實有點硬啊!得好好啃一啃了。Student3
的其餘成員就不作過多分析了,上面也有,主要分析下這個Student3
裏面的struct Student1 str
,這是一個嵌套的結構體
,結構體裏面還嵌套了一個結構體。成員struct Student1 str
其實就是Student1結構體
,上面我已經知道了Student1
的內存大小是24
了,而Student3
裏面的其餘成員所佔用的是19
,Student1
裏面成員的最大值是8
,因此offert
必須是8
的倍數,也就是從24
開始,連續開闢24
字節的內存空間來存儲struct Student1 str
。
爲了便於理解,畫了下面👇這張圖
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 輸出文件
查看底層結構,對象的內存對齊是16字節對齊
class_getInstanceSize
查看malloc_size
查看struct
)(或聯合(union
))的數據成員,第⼀個數據成員放在offset
爲0的地⽅,之後每一個數據成員存儲的起始位置要從該成員⼤⼩或者成員的⼦成員⼤⼩(只要該成員有⼦成員,⽐如說是數組,結構體等)的整數倍開始(⽐如int
爲4
字節,則要從4的整數倍地址開始存儲。 6. 若是⼀個結構⾥有某些結構體成員 ,則結構體成員
要從其內部最⼤元素
⼤⼩的整數倍地址開始存儲,也就是按8字節對齊
,由於指針的大小就8
4. 結構體的總⼤⼩
,必須是其內部最⼤成員的整數倍
,不⾜的要補⻬
。
🌹請收藏+關注,評論 + 轉發,以避免你下次找不到我,哈哈😁🌹
🌹歡迎你們留言交流,批評指正,互相學習😁,提高自我🌹