iOS開發之結構體底層探索

說在前面

咱們平時寫的代碼Objective-C,底層實現其實都是C/C++的代碼實現的,高級語言通過編譯器編譯,最終轉化爲機器語言。 在這裏插入圖片描述 因此,咱們的Objective-C的面向對象,其實都是基於C/C++的數據結構實現的。那麼Objective-C的對象、類主要是基於C/C++的什麼數據結構實現的呢?算法

1.對象的本質

那究竟是什麼樣的數據結構結構?是數組嗎?咱們都知道數組只能存儲同一種類型的數據,而對象會有不一樣的屬性,好比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的底層是結構體 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個字節。

2.結構體

下面的這個代碼是實例化出了一對象obj,其實底層實現就是上面👆講的一個結構體,裏面會有一個 Class isa成員變量

NSObject *obj = [[NSObject alloc]init];
複製代碼

假如isa地址是0x12300001obj這個指針的地址是多少呢??? alloc已經分配了內存,那麼這個指針就是首地址,而裏面只有一個成員變量,那麼isa的地址就是結構體在內存中的地址,那麼obj=0x12300001。 結構體的大小是指針的大小,那NSObject對象在內存中的大小是多少呢?是否是也是8個字節呢???其實不是的,是16字節。啊???爲何是16字節呢???一臉疑惑臉🤔,那咱們接着往下探索👇

3.內存對齊

咱們能夠打印看看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是繼承自NSObject,Student的結構體裏面有個成員變量isa是指向父類NSObject的。isa8個字節,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字節對齊,咱們繼續往下探索👇

4.內存對⻬的原則

  1. 數據成員對⻬規則:結構(struct)(或聯合(union))的數據成員,第

⼀個數據成員放在offset爲0的地⽅,之後每一個數據成員存儲的起始位置要 從該成員⼤⼩或者成員的⼦成員⼤⼩(只要該成員有⼦成員,⽐如說是數組, 結構體等)的整數倍開始(⽐如int字節,則要從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的結果,必須是其內部最⼤ 成員的整數倍不⾜的要補⻬。

5.結構體內存對齊

桂花上代碼,先上幾個結構體嚐嚐味兒,哈哈😁

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表示大小)

  1. a: 佔8個字節,offert從0開始, min(0,8), 即0 ~ 7 存放a
  2. b: 佔1個字節,offert從8開始, min(8,1), 即8 ~ 8 存放b
  3. c: 佔4個字節,offert從12開始,min(12,4),即12 ~ 15 存放c,中間九、十、11不是4的倍數,因此得空出來。
  4. d: 佔2個字節,offert從14開始,min(16,2),即16~17 存放d

下面👇放上Student1的內存分佈圖,便於理解

Student1內存分佈

根據對齊原則3,結構體的總⼤⼩,必須是其內部最⼤

成員的整數倍不⾜的要補⻬Student1中最大的是8,因此最後爲24

Student2內存大小分析

  1. a: 佔8個字節,offert從0開始, min(0,8), 即0 ~ 7 存放a
  2. b: 佔4個字節,offert從8開始, min(8,4), 即8 ~ 11 存放b
  3. c: 佔1個字節,offert從12開始,min(12,1),即12 ~ 12 存放c
  4. d: 佔2個字節,offert從14開始,min(14,2),即14~15 存放d

下面👇放上Student2的內存分佈圖,便於理解

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內存大小分析

  1. a: 佔8個字節,offert從0開始, min(0,8), 即0 ~ 7 存放a
  2. b: 佔4個字節,offert從8開始, min(8,4), 即8 ~ 11 存放b
  3. c: 佔1個字節,offert從12開始,min(12,1),即12 ~ 12 存放c
  4. d: 佔2個字節,offert從14開始,min(14,2),即14~15 存放d
  5. e: 佔4個字節,offert從16開始,min(16,4),即16~19 存放e

這道Student3菜確實有點硬啊!得好好啃一啃了。Student3的其餘成員就不作過多分析了,上面也有,主要分析下這個Student3裏面的struct Student1 str,這是一個嵌套的結構體,結構體裏面還嵌套了一個結構體。成員struct Student1 str其實就是Student1結構體,上面我已經知道了Student1的內存大小是24了,而Student3裏面的其餘成員所佔用的是19Student1裏面成員的最大值是8,因此offert必須是8的倍數,也就是從24開始,連續開闢24字節的內存空間來存儲struct Student1 str

爲了便於理解,畫了下面👇這張圖

Student3內存大小分析

6.總結

  1. 對象的本質是結構體,可使用命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件 -o 輸出文件查看底層結構,對象的內存對齊是16字節對齊
  2. 獲取類的成員變量的大小用:class_getInstanceSize 查看
  3. 獲取一個對象實際開闢的內存大小用:malloc_size查看
  4. 結構(struct)(或聯合(union))的數據成員,第⼀個數據成員放在offset爲0的地⽅,之後每一個數據成員存儲的起始位置要從該成員⼤⼩或者成員的⼦成員⼤⼩(只要該成員有⼦成員,⽐如說是數組,結構體等)的整數倍開始(⽐如int字節,則要從4的整數倍地址開始存

儲。 6. 若是⼀個結構⾥有某些結構體成員 ,則結構體成員要從其內部最⼤元素⼤⼩的整數倍地址開始存儲,也就是按8字節對齊,由於指針的大小就8 4. 結構體的總⼤⼩,必須是其內部最⼤成員的整數倍不⾜的要補⻬

🌹請收藏+關注,評論 + 轉發,以避免你下次找不到我,哈哈😁🌹

🌹歡迎你們留言交流,批評指正,互相學習😁,提高自我🌹

相關文章
相關標籤/搜索