OC和C_C++OC和C_C++
- 一個(gè)NSObject對(duì)象占多少內(nèi)存
NSObject *person = [[NSObject alloc] init];
也就是說person指針指向的這段內(nèi)存空間,占有多少內(nèi)存空間?
要想知道他在內(nèi)存中占有多少空間转培,就要知道他在內(nèi)存是怎么布局的恶导,在內(nèi)存中包含哪些內(nèi)容,搞清楚這段代碼的本質(zhì)是什么浸须,
我們平時(shí)編寫的OC代碼惨寿,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼,
編譯器會(huì)將C\C++轉(zhuǎn)成匯編,然后再轉(zhuǎn)成機(jī)器語言運(yùn)行
所以O(shè)bjective-C的面向?qū)ο蠖际腔贑\C++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的
思考:Objective-C的對(duì)象删窒、類主要是基于C\C++的什么數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的裂垦?
結(jié)構(gòu)體
將Objective-C代碼轉(zhuǎn)換為C\C++代碼
可以安裝一個(gè)gotocell可以快速定位到終端
代碼之后之間的轉(zhuǎn)換肯定是編譯器編譯的結(jié)果,所以要用編譯器相關(guān)的工具肌索,這里使用使用的clang缸废,clang是xcode內(nèi)置的編譯器llvm的編譯器前端
clang -rewrite-objc main.m -o main.cpp
因?yàn)槲覀兩晌募莄和c++都有的所有最好生成CPP文件(c plus plus),
不建議直接用上面的來直接轉(zhuǎn),因?yàn)榫幾g也要看我們要轉(zhuǎn)成什么平臺(tái)的代碼驶社,不同平臺(tái)支持的代碼肯定是不一樣的企量,因?yàn)槲覀兊拇a會(huì)轉(zhuǎn)成匯編,會(huì)變得運(yùn)行要根據(jù)硬件不同來運(yùn)行亡电,所以我們只希望生成IOS平臺(tái)來生成届巩,
模擬器(i386) 32bit(armv7),64bit(arm64)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
沒有指定架構(gòu)的生成文件的大小3.5M,指定之后只有1M多份乒,
NSObject對(duì)象的內(nèi)存本質(zhì)
我們上面生成文件恕汇,就是想看看 [[NSObject alloc] init];他的本質(zhì)是什么腕唧,
我們?cè)傧嚓P(guān)的文件中可以找到NSObject_IMPL
struct NSObject_IMPL {
Class isa;
};
如果將我們cpp拖進(jìn)xcode項(xiàng)目,編譯會(huì)報(bào)錯(cuò)瘾英,因?yàn)閏pp是臨時(shí)生成的枣接,還有一個(gè)就是cpp文件也有一個(gè)main函數(shù),一個(gè)程序只能有一個(gè)main函數(shù)缺谴,所有也會(huì)出錯(cuò)但惶,
可以再Xcode的編譯文件中,將cpp去掉湿蛔,
NSObject_IMPL他的意思就是NSObject Implementation膀曾,也就是一個(gè)NSObject的底層實(shí)現(xiàn),
如果我們直接通過Xcode點(diǎn)進(jìn)去看一下NSObject的實(shí)現(xiàn)可以發(fā)現(xiàn)
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end
簡(jiǎn)化成
@interface NSObject {
Class isa ;
}
@end
OC的類他的底層實(shí)現(xiàn)就是結(jié)構(gòu)體阳啥,C\C++的結(jié)構(gòu)體支撐的OC的面向?qū)ο?/p>
-
問題一個(gè)OC對(duì)象內(nèi)存中是如何布局的添谊?
NSObject的底層實(shí)現(xiàn),頭文件里面
截屏2020-10-18 下午7.26.57.png
底層實(shí)現(xiàn)
截屏2020-10-18 下午7.34.12.png
isa 我們可以按進(jìn)去看一下,他的類型是這個(gè)樣子,是一個(gè)指向結(jié)構(gòu)體的指針
typedef struct objc_class *Class;
既然isa是一個(gè)指針那么他在64bit的機(jī)器占據(jù)8個(gè)字節(jié)察迟,32bit4個(gè)字節(jié)斩狱,
所以alloc相當(dāng)于給右邊的結(jié)構(gòu)體分配存儲(chǔ)空間,分配完成之后扎瓶,會(huì)又一個(gè)指針所踊,指向我們的分配的這段空間,假設(shè)我們分配的空間isa的地址0x100400110
class_getInstanceSize栗弟、malloc_size.
一個(gè)NSObject對(duì)象占多少內(nèi)存?
根據(jù)我們上面代碼分析工闺,我們可能會(huì)覺得一個(gè)NSObject占用8個(gè)字節(jié)的內(nèi)存乍赫,實(shí)際上不是,實(shí)際上是16個(gè)字節(jié)陆蟆,但是他利用起來的只有8個(gè)字節(jié)的大小雷厂,我們可以用runtime來驗(yàn)證一下,
class_getInstanceSize
用來獲取一個(gè)類的實(shí)力對(duì)象的大小叠殷,實(shí)例就是我們通過alloc出來的具體對(duì)象改鲫,
malloc_size
獲得指針?biāo)赶虻膬?nèi)存大小
//獲得NSObject的實(shí)例對(duì)象的成員變量所占用的大小
NSLog(@"%zu大小", class_getInstanceSize([NSObject class]));//8字節(jié)
//獲得指針?biāo)赶虻膬?nèi)存大小
NSLog(@"%zd", malloc_size(CFBridgingRetain(person)));// 16字節(jié)
蘋果開源網(wǎng)站 opensource.apple.com->objc(https://opensource.apple.com/tarballs/objc4/)找到最新的進(jìn)行下載
解壓之后我們直接打開項(xiàng)目,搜索class_getInstanceSize查看源碼發(fā)現(xiàn)林束,他會(huì)調(diào)用一個(gè)
返回就是實(shí)例對(duì)象的成員變量的大小
實(shí)際內(nèi)存中
面試題回答
我們還是回到IOS的源碼
通過alloc查看他是否真的是占用了16個(gè)字節(jié)像棘,實(shí)際上調(diào)用了allocWithZone
在它里面調(diào)用了calloc函數(shù),這里需要傳一個(gè)size參數(shù),
所以一旦發(fā)現(xiàn)小于16他就會(huì)分配16個(gè)字節(jié)壶冒,因?yàn)樗?guī)定所有的對(duì)象最低是16個(gè)字節(jié)缕题,這是corefoundation硬性規(guī)定的
- 一個(gè)NSObject對(duì)象占多少內(nèi)存?
系統(tǒng)分配了16個(gè)字節(jié)給NSObject對(duì)象(通過malloc_size函數(shù)獲得)
但NSObject對(duì)象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下胖腾,可以通過class_getInstanceSize函數(shù)獲得)
窺探NSObject的內(nèi)存
我們可以從另一個(gè)角度去驗(yàn)證他是否占了16個(gè)字節(jié)的內(nèi)存,Xcode工具烟零,打斷點(diǎn)瘪松,查看對(duì)象的地址
然后通過debug->debugworkflow->view memory
因?yàn)閮?nèi)存使用的是16進(jìn)制,所以兩位占一個(gè)字節(jié)锨阿,
通過內(nèi)存圖我們可以看出來我們的對(duì)象在內(nèi)存的分布形式,有顏色的部分就是我們的NSObject的內(nèi)存分布宵睦,可以看出來,他只是用了前8個(gè)字節(jié)
我們也可以通過打斷點(diǎn)之后使用
(lldb)
調(diào)試器來進(jìn)行調(diào)試
lldb常用的指令
print墅诡、p
:打印
po
:打印對(duì)象
內(nèi)存讀取
memory read
/數(shù)量
格式字節(jié)數(shù)
內(nèi)存地址
x/數(shù)量格式字節(jié)數(shù) 內(nèi)存地址
x/3xw 0x10010
-
格式
x是16進(jìn)制壳嚎,f是浮點(diǎn),d是10進(jìn)制字節(jié)大小 b:byte 1字節(jié)书斜,h:half word 2字節(jié) w:word 4字節(jié)诬辈,g:giant word 8字節(jié)
修改內(nèi)存中的值
memory write
內(nèi)存地址 數(shù)值
memory write 0x0000010 10
使用例子
Student的本質(zhì)
@interface Student : NSObject{
@public
int _no; //4字節(jié)
int _age;//4字節(jié)
}
@end
@implementation Student
@end
通過終端生成C++代碼
struct NSObject_IMPL
{
Class isa; //8字節(jié)
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
我們寫的創(chuàng)建一個(gè)對(duì)象的方法
Student *student = [[Student alloc] init];
NSLog(@"%zd", class_getInstanceSize([student class]));//16
NSLog(@"%zd", malloc_size((__bridge const void *)(student)));//16
student->_no = 12;
student->_age = 15;
在內(nèi)存中的分布圖其實(shí)是這個(gè)樣子的
因?yàn)榻Y(jié)構(gòu)體的地址就是第一個(gè)成員變量的地址,所以Student的地址就是isa的地址荐吉,內(nèi)存分布是連續(xù)的焙糟,接下來的4個(gè)字節(jié)存儲(chǔ)的_no的值,再4個(gè)是age的值样屠,
我們可以強(qiáng)制將student指針穿撮,轉(zhuǎn)成結(jié)構(gòu)體類型
struct Student_IMPL *stu = (__bridge struct Student_IMPL *)student;
NSLog(@"no=%d,age=%d", stu->_no,stu->_age);//no=12,age=15
也可以正常的訪問,進(jìn)一步說明了痪欲,他本質(zhì)上就是這個(gè)結(jié)構(gòu)體類型
CPU從內(nèi)存讀取數(shù)據(jù)的方式分為大端和小端模式悦穿,IOS就是小端模式,讀數(shù)據(jù)會(huì)從小的地址開始业踢,所以他的四個(gè)字節(jié)是 04 00 00 00栗柒,
更復(fù)雜的集成模式
//Person
@interface Person:NSObject
{
@public
int _no;
}
@end
@implementation Person
@end
//Student
@interface Student:Person
{
@public
int _age;
}
@end
-
思考:一個(gè)Person對(duì)象、一個(gè)Student對(duì)象占用多少內(nèi)存空間知举?
截屏2020-10-19 上午11.12.49.png
答案 都是16個(gè)字節(jié)
內(nèi)存對(duì)其瞬沦,結(jié)構(gòu)的內(nèi)存大小,必須是最大成員變量的倍數(shù)雇锡,
因?yàn)镻erson占居了16個(gè)字節(jié)逛钻,但是最后面的4個(gè)字節(jié)是空的所以當(dāng)Student繼承了之后,會(huì)把自己age放到最后面的4個(gè)空字節(jié)上面锰提,因此曙痘,student還是占據(jù)了16個(gè)字節(jié),
如果我們?cè)黾右粋€(gè)int類型的成員變量立肘,就會(huì)占32边坤,
@interface Student:Person //16
{
@public
int _age;//4
int _weight;//4
}
@end
我們通過alloc init出來的實(shí)例對(duì)象,是不會(huì)存儲(chǔ)方法的谅年,他只存有成員變量的值惩嘉,因?yàn)榉椒ㄔ趦?nèi)存中指存在一份就夠了,不需要每個(gè)實(shí)例都存一份
回答疑問
計(jì)算內(nèi)存地址踢故,就是一個(gè)個(gè)的往后數(shù)文黎,
12-內(nèi)存分布注意點(diǎn)
@interface Dog : NSObject
{
//struct NSObject_IMPL NSObject_IVARS;//8個(gè)字節(jié)
int _no;//4
int _age;//4
int _height;//4
} //理論上他在內(nèi)存的大小是24字節(jié)
NSLog(@"%zd", class_getInstanceSize([dog class]));//24
NSLog(@"%zd", malloc_size((__bridge const void *)(dog)));//32
因?yàn)閙alloc_size結(jié)果是32 惹苗,這個(gè)是他在內(nèi)存中被分配的大小,因?yàn)樗麅?nèi)存對(duì)其的單位是16耸峭,所以malloc的空間必須是16的倍數(shù).
13-alloc的size分析
在calloc函數(shù)中分配內(nèi)存的時(shí)候桩蓉,傳入的size確實(shí)就是實(shí)力對(duì)象的內(nèi)存大小,24劳闹,但是底層內(nèi)部的實(shí)現(xiàn)院究,會(huì)將這個(gè)歌內(nèi)存驚醒對(duì)其計(jì)算,返回一個(gè)對(duì)其之后的size在進(jìn)行實(shí)際分配本涕,對(duì)其之后就是32