匯編窺探Swift底層(四):閉包

窺探閉包的內(nèi)存

閉包:一個(gè)函數(shù)和它所捕獲的變量\常量環(huán)境組合起來,稱為閉包

    1. 首先先看一下下面這段代碼绿语,getFn()返回了一個(gè)函數(shù)钮孵,然后調(diào)用4次這個(gè)函數(shù),我們來看一下getFn()的內(nèi)部是怎么用匯編實(shí)現(xiàn)的
typealias Fn = (Int) -> Int

func getFn() -> Fn{
    func plus(_ i: Int) -> Int{
        return i
    }
    return plus
}

var fn = getFn()
print(fn(1))    輸出1
print(fn(2))    輸出2
print(fn(3))    輸出3
print(fn(4))    輸出4
    1. 下面就是getFn()方法的匯編代碼
TestSwift`getFn():
    0x100001320 <+0>:  pushq  %rbp
    0x100001321 <+1>:  movq   %rsp, %rbp
    0x100001324 <+4>:  leaq   0x15(%rip), %rax          ; plus #1 (Swift.Int) -> Swift.Int in TestEnumMemory.getFn() -> (Swift.Int) -> Swift.Int at main.swift:92
->  0x10000132b <+11>: xorl   %ecx, %ecx
    0x10000132d <+13>: movl   %ecx, %edx
    0x10000132f <+15>: popq   %rbp
    0x100001330 <+16>: retq   

    1. 其他匯編可以先忽略睡陪,我們重點(diǎn)看一下第三行的匯編leaq 0x15(%rip), %rax, 其實(shí)看后面的注釋我們就可以猜到這是什么意思了,注釋是這么寫的:; plus #1 (Swift.Int) -> Swift.Int in TestSwift.getFn() -> (Swift.Int) -> Swift.Int at main.swift:92匿情,意思就是說這句匯編的作用是算出plus函數(shù)的地址并且賦值給rax寄存器 兰迫,第一篇的時(shí)候說過rax寄存器經(jīng)常用來存放函數(shù)的返回值,所以賦值給rax寄存器的目的就是要當(dāng)做getFn()方法的返回值炬称,把函數(shù)地址返回出去汁果。
    1. 那么函數(shù)地址究竟是什么呢?leap的意思直接賦值玲躯,也就是取出rip寄存器的值加上0x15之后据德,直接把算出來的地址賦值給rax寄存器,我們第一篇的時(shí)候講過rip寄存器存放的是下一行指令的地址跷车,也就是0x10000132b棘利,加上0x15也就是0x100001340,我們也可以通過LLDB命令register read rax來讀取rax寄存器的值朽缴,結(jié)果同樣是0x100001340赡译,所以我們就可以知道getFn()方法的返回值就是0x100001340
    1. 接下來,我們對(duì)上述代碼做一點(diǎn)點(diǎn)小的改動(dòng)不铆,代碼如下蝌焚,我們要注意此時(shí),plus函數(shù)捕捉了num變量 誓斥,也就是說返回的plus函數(shù)與num變量的值組成了閉包只洒,這個(gè)時(shí)候再來看一下getFn()的匯編代碼
typealias Fn = (Int) -> Int

func getFn() -> Fn{
    var num = 0          //增加了這一行
    func plus(_ i: Int) -> Int{
        num = num + i    //增加了這一行
        return num       //返回值變成了num+i
    }
    return plus          //返回的plus函數(shù)和捕獲num產(chǎn)生的堆空間形成了閉包
}

var fn = getFn()
print(fn(1))    輸出1
print(fn(2))    輸出3
print(fn(3))    輸出6
print(fn(4))    輸出10
    1. 此時(shí),getFn()的匯編代碼是下面這個(gè)樣子
TestSwift`getFn():
    0x100001120 <+0>:  pushq  %rbp
    0x100001121 <+1>:  movq   %rsp, %rbp
    0x100001124 <+4>:  subq   $0x20, %rsp
    0x100001128 <+8>:  leaq   0x5009(%rip), %rdi
    0x10000112f <+15>: movl   $0x18, %esi
    0x100001134 <+20>: movl   $0x7, %edx
    0x100001139 <+25>: callq  0x10000543a               ; symbol stub for: swift_allocObject
    0x10000113e <+30>: movq   %rax, %rdx
    0x100001141 <+33>: addq   $0x10, %rdx
    0x100001145 <+37>: movq   %rdx, %rsi
    0x100001148 <+40>: movq   $0x0, 0x10(%rax)
->  0x100001150 <+48>: movq   %rax, %rdi
    0x100001153 <+51>: movq   %rax, -0x8(%rbp)
    0x100001157 <+55>: movq   %rdx, -0x10(%rbp)
    0x10000115b <+59>: callq  0x1000054b2               ; symbol stub for: swift_retain
    0x100001160 <+64>: movq   -0x8(%rbp), %rdi
    0x100001164 <+68>: movq   %rax, -0x18(%rbp)
    0x100001168 <+72>: callq  0x1000054ac               ; symbol stub for: swift_release
    0x10000116d <+77>: movq   -0x10(%rbp), %rax
    0x100001171 <+81>: leaq   0x1e8(%rip), %rax         ; partial apply forwarder for plus #1 (Swift.Int) -> Swift.Int in TestSwift.getFn() -> (Swift.Int) -> Swift.Int at <compiler-generated>
    0x100001178 <+88>: movq   -0x8(%rbp), %rdx
    0x10000117c <+92>: addq   $0x20, %rsp
    0x100001180 <+96>: popq   %rbp
    0x100001181 <+97>: retq   

    1. 上下的匯編代碼對(duì)比可知劳坑,僅僅因?yàn)榻M成了閉包毕谴,getFn()的匯編代碼就多了很多,現(xiàn)在我們來觀察一下這個(gè)匯編的第七句callq 0x10000543a距芬,這句匯編的注釋是symbol stub for: swift_allocObject涝开,也就是說這句匯編開辟了堆空間,前邊說過返回值是存儲(chǔ)在rax寄存器中的框仔,也就是說現(xiàn)在的rax寄存器存放的是開辟的這段堆空間舀武。
    1. 我們用LLDB命令register read rax得到了這段堆空間的地址是rax = 0x0000000100697b10,然后我們用LLDB命令x/5xg來查看一下這段堆空間到底存放了什么离斩,存放的數(shù)據(jù)如下:
(lldb) x/5xg  0x0000000100697b10
0x100697b10: 0x0000000100006138 0x0000000000000002
0x100697b20: 0x0000000000000000 0x0002000000000000
0x100697b30: 0x0000000000000000
    1. 我們大膽猜測(cè)一下银舱,這段堆空間究竟存放這什么東西瘪匿,由于是plus函數(shù)中捕獲了num變量,之后匯編中才增加了開辟堆空間的指令寻馏,所以堆空間的東西一定和num相關(guān)棋弥,從輸出結(jié)果是1、3诚欠、6顽染、10可以看出來,訪問的num是同一個(gè)num轰绵,所以很有可能是開辟了一段堆空間來存放num變量的值粉寞,也就是把num的值復(fù)制了一份放到了堆空間,方便以后的訪問藏澳,num是局部變量仁锯,在函數(shù)調(diào)用之后局部變量num就會(huì)被銷毀掉耀找。
    1. 我們?cè)趐lus函數(shù)內(nèi)部再打一個(gè)斷點(diǎn)翔悠,觀察一下每次num = num + 1后 ,剛才那段堆空間的值是否發(fā)生了變化
調(diào)用fn(1)后野芒,堆空間的數(shù)據(jù)變成了下面的樣子
(lldb) x/5xg  0x0000000100697b10
0x100697b10: 0x0000000100006138 0x0000000200000002
0x100697b20: 0x0000000000000001 0x0002000000000000
0x100697b30: 0x0000000000000000
調(diào)用fn(2)后蓄愁,堆空間的數(shù)據(jù)變成了下面的樣子
(lldb) x/5xg  0x0000000100697b10
0x100697b10: 0x0000000100006138 0x0000000200000002
0x100697b20: 0x0000000000000003 0x0002000000000000
0x100697b30: 0x0000000000000000
調(diào)用fn(3)后,堆空間的數(shù)據(jù)變成了下面的樣子
(lldb) x/5xg  0x0000000100697b10
0x100697b10: 0x0000000100006138 0x0000000200000002
0x100697b20: 0x0000000000000006 0x0002000000000000
0x100697b30: 0x0000000000000000
調(diào)用fn(4)后狞悲,堆空間的數(shù)據(jù)變成了下面的樣子
(lldb) x/5xg  0x0000000100697b10
0x100697b10: 0x0000000100006138 0x0000000200000002
0x100697b20: 0x000000000000000a 0x0002000000000000
0x100697b30: 0x0000000000000000
    1. 從上面可以看出來我們的1撮抓、3、6摇锋、10丹拯,確實(shí)在這段堆空間里,也就證實(shí)了我們的想法荸恕,形成閉包之后getFn()內(nèi)部會(huì)開辟一段堆空間乖酬,用來存放捕獲的變量
    1. 那么這段堆空間究竟有多大呢融求,首先我們知道堆空間分配的內(nèi)存是以16字節(jié)為單位的咬像,也就是說是16的倍數(shù),然后我們觀察callq 0x10000543a分配堆空間的前兩句匯編:movl $0x18, %esi生宛,$0x7, %edx县昂,我們以前說過rsi寄存器rdx寄存器都是用來存放參數(shù)的陷舅,而esi寄存器不就是rsi寄存器的的其中4個(gè)字節(jié)的空間嘛倒彰,所以esi寄存器中存放的0x18就是要傳給swift_allocObject函數(shù)的參數(shù),同理莱睁,edx寄存器中存放的0x7也是swift_allocObject函數(shù)的參數(shù)狸驳,轉(zhuǎn)化成十進(jìn)制预明,也就是說把24和7作為參數(shù)給swift_allocObject函數(shù),可以直接告訴大家耙箍,這里的就是堆空間實(shí)際占用的字節(jié)數(shù)撰糠,由于堆空間的內(nèi)存必須是16的倍數(shù),所以這塊堆空間一共分配了32個(gè)字節(jié)辩昆。
    1. 其實(shí)閉包產(chǎn)生的這段堆空間初始化類對(duì)象產(chǎn)生的堆空間阅酪,非常相似,前8個(gè)字節(jié)存儲(chǔ)的都是類型信息汁针,再往后8個(gè)字節(jié)存儲(chǔ)的是引用計(jì)數(shù)相關(guān)术辐,剩下的才是我們要存儲(chǔ)的數(shù)據(jù),所以上面的閉包代碼施无,你可以認(rèn)為與下面的代碼是等價(jià)的辉词。
class Closure{
    var num = 0
    func plus(_ i: Int) -> Int{
        num = num + i
        return num
    }
}
var closure = Closure()
print(closure.plus(1))  輸出1
print(closure.plus(2))  輸出3
print(closure.plus(3))  輸出6
print(closure.plus(4))  輸出10
    1. 我們要分清閉包閉包表達(dá)式區(qū)別
    - 1>. 閉包:一個(gè)函數(shù)和它所捕獲的變量\常量環(huán)境組合起來,稱為閉包猾骡,本文章中瑞躺,plus函數(shù)和它為了存儲(chǔ)num的值而分配的堆空間組合起來稱之為閉包。
    - 2>. 閉包表達(dá)式:用簡(jiǎn)潔語(yǔ)法構(gòu)建內(nèi)聯(lián)閉包的方式兴想,可以用閉包表達(dá)式來定義一個(gè)函數(shù)幢哨,閉包表達(dá)式的格式是這樣的:{ (參數(shù)列表) -> 返回值類型 in 函數(shù)體代碼}
15. 總結(jié)
    1. 閉包會(huì)對(duì)用到的局部變量進(jìn)行捕獲,也就是會(huì)把局部變量的值放到開辟的堆空間中嫂便,以防止局部變量銷毀了導(dǎo)致值無(wú)法使用
    1. 閉包會(huì)對(duì)用到的對(duì)象引用計(jì)數(shù)+1捞镰,防止對(duì)象被提前釋放掉,不會(huì)再分配堆空間了毙替,岸售。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市厂画,隨后出現(xiàn)的幾起案子凸丸,更是在濱河造成了極大的恐慌,老刑警劉巖木羹,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甲雅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坑填,警方通過查閱死者的電腦和手機(jī)抛人,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脐瑰,“玉大人妖枚,你說我怎么就攤上這事〔栽冢” “怎么了绝页?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵荠商,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我续誉,道長(zhǎng)莱没,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任酷鸦,我火速辦了婚禮饰躲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘臼隔。我一直安慰自己嘹裂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布摔握。 她就那樣靜靜地躺著寄狼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氨淌。 梳的紋絲不亂的頭發(fā)上泊愧,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音宁舰,去河邊找鬼拼卵。 笑死奢浑,一個(gè)胖子當(dāng)著我的面吹牛蛮艰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雀彼,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼壤蚜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了徊哑?” 一聲冷哼從身側(cè)響起袜刷,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎莺丑,沒想到半個(gè)月后著蟹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梢莽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年萧豆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昏名。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涮雷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轻局,到底是詐尸還是另有隱情洪鸭,我是刑警寧澤样刷,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站览爵,受9級(jí)特大地震影響置鼻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜓竹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一沃疮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梅肤,春花似錦司蔬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至左医,卻和暖如春授帕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浮梢。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工跛十, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秕硝。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓芥映,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親远豺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奈偏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353