一. 匯編分析String底層
Mach-O文件是iOS的可執(zhí)行文件,我們平時(shí)寫(xiě)的代碼都在Mach-O,所以我們窺探Mach-O文件喜喂,就相當(dāng)于窺探內(nèi)存了(因?yàn)镸ach-O文件載入內(nèi)存不會(huì)有太大變化瓤摧,只不過(guò)內(nèi)存是動(dòng)態(tài)更新的),如下圖:
問(wèn)題1
1個(gè)String變量占用多少內(nèi)存玉吁?
下面2個(gè)String變量照弥,底層存儲(chǔ)有什么不同?
var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
運(yùn)行如下代碼:
var str1 = "0123456789ABCDE"
print(MemoryLayout.stride(ofValue: str1)) //16 實(shí)際分配16字節(jié)
print(Mems.memStr(ofVal: &str1))
打咏薄:
16
0x3736353433323130 0xef45444342413938
通過(guò)打印可知:
16:str1實(shí)際分配16字節(jié)这揣,
0x3736353433323130 0xef45444342413938 :這是打印str1指針指向內(nèi)存存儲(chǔ)的東西,0x代表16進(jìn)制影斑,e代表直接把字符內(nèi)容存儲(chǔ)到內(nèi)存中给赞,f代表長(zhǎng)度15,通過(guò)查詢(xún)ASCII表可知0代表30矫户,1代表31......等等
總結(jié):
當(dāng)字符串長(zhǎng)度小于等于15片迅,1個(gè)字節(jié)留著存放長(zhǎng)度,另外15字節(jié)直接存放字符串的ASCII值皆辽,類(lèi)似于OC的tagger pointer技術(shù)
如果字符串再多一位呢柑蛇?
var str2 = "0123456789ABCDEF"
print(MemoryLayout.stride(ofValue: str2)) //16 實(shí)際分配16
print(Mems.memStr(ofVal: &str2))
打印:
16
0xd000000000000010 0x800000010000a790
可以發(fā)現(xiàn)驱闷,當(dāng)字符串長(zhǎng)度大于15耻台,就不是直接存儲(chǔ)字符串的值了
MJ老師通過(guò)窺探匯編得出如下結(jié)果,對(duì)于0xd000000000000010 0x800000010000a790:
- 前8字節(jié)存放的是字符創(chuàng)的長(zhǎng)度10空另,在16進(jìn)制中就是16
- 后8個(gè)字節(jié)存放的是:0x800000010000a790 = 字符串的真實(shí)地址 + 0x7fffffffffffffe0盆耽,所以字符串的真實(shí)地址 = 0x800000010000a790 - 0x7fffffffffffffe0(小技巧:字符串的真實(shí)地址 = 0x000000010000a790 + 0x20)
通過(guò)計(jì)算得出0x10000A7B0是"0123456789ABCDEF"的真實(shí)地址,讀取這個(gè)地址的內(nèi)存扼菠,如下:
x 0x10000a7b0: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 0123456789ABCDEF
可以發(fā)現(xiàn)摄杂,這個(gè)內(nèi)存地址存儲(chǔ)的的確是str2字符串。
- 使用MachoView
那個(gè)這個(gè)地址:0x10000A7B0在哪呢娇豫?
這就要用到文章開(kāi)頭的知識(shí)了匙姜,我們窺探Mach-O文件畅厢,就相當(dāng)于窺探內(nèi)存冯痢,在Mach-O文件中我們可以知道0x10000A7B0地址存放在哪里
補(bǔ)充:
0x100000000: VM Address 虛擬地址
0x10000A7B0 = 0x100000000 + 0xA7B0
0xA7B0是Mach文件的地址,所以我們?cè)贛ach-O文件中找0xA7800就好了
運(yùn)行程序框杜,使用MachoView打開(kāi)程序的可執(zhí)行文件浦楣,可以發(fā)現(xiàn)0xA7B0放在常量區(qū),如下:
總結(jié):
字符串長(zhǎng)度 <= 0xF(15)咪辱,字符串內(nèi)容直接存放在str1變量的內(nèi)存中(比如:var str1 = "0123456789")
字符串長(zhǎng)度 > 0xF(15)振劳,字符串內(nèi)容存放在__TEXT.cstring中(常量區(qū))
字符串的地址值信息存放在str2變量的后8個(gè)字節(jié)中(比如:var str2 = "0123456789ABCDEFGHIJ")
問(wèn)題2
如果對(duì)String進(jìn)行拼接操作,String變量的存儲(chǔ)會(huì)發(fā)生什么變化油狂?
var str1 = "0123456789"
var str2 = "0123456789ABCDEF"
str1.append("ABCDE")
str1.append("F")
str2.append("G")
運(yùn)行如下代碼:
var str1 = "01234567"
print(Mems.memStr(ofVal: &str1))
str1.append("GIHJ")
print(Mems.memStr(ofVal: &str1))
打永帧:
0x3736353433323130 0xe800000000000000
0x3736353433323130 0xec0000004a484947
可以發(fā)現(xiàn)寸癌,當(dāng)字符串進(jìn)行拼接的時(shí)候,如果拼接后字符串長(zhǎng)度還是小于等于15弱贼,那么拼接的字符串還會(huì)放在原來(lái)的后面
如果字符串長(zhǎng)度本來(lái)是16蒸苇,拼接后大于16呢?
var str2 = "0123456789ABCDEF"
print(Mems.memStr(ofVal: &str2))
str2.append("G")
print(Mems.memStr(ofVal: &str2))
打铀甭谩:
0xd000000000000010 0x8000000100006620 //16字節(jié)溪烤,放常量區(qū)
0xf000000000000011 0x000000010068af60 //大于16字節(jié),放堆空間
根據(jù)上面的小技巧庇勃,0x000000010068af60 + 0x20得到字符串的真實(shí)地址:0x000000010068af80檬嘀,查看真實(shí)地址內(nèi)存,發(fā)現(xiàn)存儲(chǔ)的的確是上面的字符串
(lldb) x 0x000000010068af80
0x10068af80: 30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46 0123456789ABCDEF
0x10068af90: 47 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 G...............
(lldb)
如何證明上面str2.append("G")之后是存放在堆空間责嚷?
很簡(jiǎn)單鸳兽,在malloc函數(shù)打個(gè)斷點(diǎn),如果走malloc函數(shù)罕拂,就說(shuō)明在堆空間
MJ老師在匯編里面在malloc函數(shù)打個(gè)斷點(diǎn)贸铜,執(zhí)行str2.append("G")發(fā)現(xiàn)的確走了malloc,說(shuō)明str2.append("G")之后是存放在堆空間
大總結(jié):
現(xiàn)在可以回答開(kāi)頭兩個(gè)問(wèn)題了
var str1 = "0123456789"
字符串長(zhǎng)度 <= 0xF(15)聂受,字符串內(nèi)容直接存放在str1變量的內(nèi)存中
var str2 = "0123456789ABCDEF"
字符串長(zhǎng)度 > 0xF(15)蒿秦,字符串內(nèi)容存放在__TEXT.cstring中(常量區(qū))
字符串的地址值信息存放在str2變量的后8個(gè)字節(jié)中
str1.append("ABCDE")
由于字符串長(zhǎng)度 <= 0xF,所以字符串內(nèi)容依然存放在str1變量的內(nèi)存中
str1.append("F")
開(kāi)辟堆空間
可能你會(huì)疑問(wèn)這里為什么是開(kāi)辟堆空間蛋济?
拼接之前str1是0123456789ABCDE棍鳖,這時(shí)候是字符串15字節(jié)+1字節(jié)(存放長(zhǎng)度),16個(gè)字節(jié)已經(jīng)滿(mǎn)了碗旅,所以無(wú)法拼接渡处。
那么放常量區(qū)呢?更不可以祟辟,因?yàn)槌A繀^(qū)的內(nèi)容不可以改医瘫,所以只能開(kāi)辟堆空間。
str2.append("G")
開(kāi)辟堆空間
二. 匯編分析Array底層
關(guān)于Array的思考
public struct Array<Element>
var arr = [1, 2, 3, 4]
1個(gè)Array變量占用多少內(nèi)存旧困?
數(shù)組中的數(shù)據(jù)存放在哪里醇份?