Swift匯編分析閉包-內(nèi)存布局

1休傍、閉包表達(dá)式與閉包

閉包表達(dá)式也就是定義一個函數(shù)。
一般我們可以通過func定義一個函數(shù)卷中,也可以通過閉包表達(dá)式定義一個函數(shù)氓涣。
閉包與閉包表達(dá)式的區(qū)別是閉包會捕獲外部變量/常量,但有時候會將閉包表達(dá)式也稱為閉包达传,但實際上只是簡稱篙耗。

func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}

進(jìn)一步寫成閉包表達(dá)式:
var sum = {
    (v1: Int, v2: Int) -> Int in
    v1 + v2
}


可以看到上面的函數(shù)與閉包表達(dá)式的作用是一樣的。
而閉包表達(dá)式也是可以作為函數(shù)的參數(shù)

func test(v1: Int, v2: Int, fn: (Int, Int) -> Int) -> Int {
    return fn(v1, v2)
}

test(v1: 1, v2: 2, fn: sum)

也可以這樣寫宪赶,也就是直接傳入閉包表達(dá)式:
test(v1: 1, v2: 2, fn: {
    (v1: Int, v2: Int) -> Int in
    v1 + v2
})

可以更簡化一步
test(v1: 1, v2: 2, fn: {
    v1, v2 in
    v1 + v2
})

再來簡化一下:
test(v1: 1, v2: 2, fn: {
    $0 + $1
})

甚至于直接傳入一個+號
test(v1: 1, v2: 2, fn: +)


尾隨閉包
如果將閉包表達(dá)式作為函數(shù)的最后一個實參宗弯,使用尾隨閉包可以增強函數(shù)的可讀性。
因此在我們實際寫代碼的時候編譯器有時候會提示將最后的閉包表達(dá)式修改為尾隨閉包的形式搂妻。
上面所述的例子既可以寫成尾隨閉包的形式

test(v1: 1, v2: 3) {
    v1, v2 in
    v1 + v2
}

test(v1: 1, v2: 3) {
    $0 + $1
}

如果閉包表達(dá)式是函數(shù)的唯一實參蒙保,而且使用了尾隨閉包的語法,那么不需要在函數(shù)后寫圓括號欲主。

2邓厕、閉包

一個函數(shù)和它捕獲的變量常量環(huán)境組合起來稱為閉包
一般情況下這個函數(shù)是定義在函數(shù)內(nèi)部的,也就是這個內(nèi)部的函數(shù)與這個包裹其的函數(shù)內(nèi)部定義的變量一起稱之為閉包扁瓢。
如下面的例子plus以及其捕獲的變量a一起才是閉包详恼。
下面我們先看一下代碼,可以看到這里內(nèi)部的函數(shù)捕獲變量a


typealias Fn = (Int) -> (Int)

var a = 10
func exec() -> Fn {
    func plus (_ i: Int) -> Int {
        a += i
        return a
    }
    return plus
}


let fn = exec()
print(fn(1))
print(fn(2))

此時可以想一下打印的結(jié)果是多少

11
13

因為這里的a是一個全局變量引几,因此我們調(diào)用了兩次訪問的都是同一個變量地址昧互,因此這里的結(jié)果顯而易見了。
那如果a是一個局部變量呢伟桅?

func exec() -> Fn {
    var a = 10
    func plus (_ i: Int) -> Int {
        a += i
        return a
    }
    return plus
}

此時我們再去調(diào)用的結(jié)果是什么敞掘?
實際上這里的結(jié)果與上面一致,按道理我們在執(zhí)行完exec函數(shù)獲得返回值fn之后其內(nèi)部的變量a應(yīng)該是會被釋放掉的楣铁。
此時我們添加斷點到 return plus然后查看匯編代碼玖雁。

image.png

此時我們可以看到在第9行處調(diào)用了一個函數(shù),后邊有描述為swift_allocObject民褂,這說明了為其在堆空間分配了內(nèi)存茄菊。而實際上我們的變量都是局部變量,按道理應(yīng)該是在棧上使用赊堪,使用完后就會被銷毀釋放掉面殖。也就是我們在返回plus時,捕獲了外部變量哭廉,但是這個變量是局部變量脊僚,因為為了延長變量a的生命周期因為將其放入了堆空間。
那么之前打印結(jié)果的問題也就解釋了,我們調(diào)用一次exec辽幌,此時就會分配一次堆空間增淹,堆空間內(nèi)存放著的就是變量的值。因此我們每次調(diào)用fn的時候使用了同一個變量乌企。
那么我們再調(diào)用exec獲取一個函數(shù)

let fn = exec()
print(fn(1))
print(fn(2))

let fn1 = exec()
print(fn1(3))
print(fn1(4))

此時的打印結(jié)果也就知道虑润,這里是分開的兩個堆空間。
那么我們來看一下當(dāng)前的堆空間中是否是存放著局部變量加酵。
此時也在return a處添加上斷點拳喻,執(zhí)行然后進(jìn)入到匯編代碼,
因為我們在分配堆空間處也有斷點

image.png

因此我們著這個斷點處查看一下當(dāng)前堆空間的地址猪腕。之前我們也提到過call函數(shù)后的返回值一般都是在rax中冗澈,所以這里我們只需要讀取一下rax中的值即可。

(lldb) register read rax
     rax = 0x000000010055f0e0

實際上這個地址存放的也就是變量a的值陋葡,但是這塊地址是剛剛分配的亚亲,也就是這里面還沒有數(shù)據(jù)或者有的也是垃圾數(shù)據(jù)。
此時我們進(jìn)入到上面提到過的return a處的斷點腐缤,根據(jù)代碼我們是return了一個變量捌归,那么也就是這個變量實際上已經(jīng)是有值的,此時我們查看上面分配的堆空間里邊的數(shù)據(jù)

(lldb) x/5xg 0x000000010055f0e0
0x10055f0e0: 0x0000000100008158 0x0000000200000002
0x10055f0f0: 0x000000000000000b 0x0000000000000000
0x10055f100: 0x0000000000000000

前16個字節(jié)不用管柴梆,直接從第17個字節(jié)開始看陨溅,可以看到我們第一次執(zhí)行fn(1)的結(jié)果存放在里邊终惑。所以這里也就印證了前面所說的分配的堆空間存放的正是變量a的值绍在。
同時我們也可以返回再看一下exec()的匯編截圖中的第13行處movq $0xa, 0x10(%rax),這是是有個move執(zhí)行操作雹有,上述我們知道rax中存放著變量的堆空間地址偿渡,這里是直接將值賦值到了這個地址的第3個8個字節(jié),也就是跳過了前面的16個字節(jié)霸奕。也就是直接將這個變量從椓锟恚空間拷貝到了堆空間。

那么這里分配了多大的堆空間呢质帅。我們再回到exec匯編的截圖處适揉,此時在swift_allocObjec之前我們可以看到對應(yīng)有3個參數(shù)傳遞rdirsi煤惩、rdx嫉嘀,但是我們再看一其前面?zhèn)鬟f的數(shù)據(jù)大概可以知道 rsi里邊才是存放的需要的空間大小為24,但是因為內(nèi)存對齊的緣故魄揉,所以應(yīng)該是分配了32個字節(jié)剪侮。

rax、rdx常作為函數(shù)返回值使用
rdi洛退、rsi瓣俯、rdx杰标、rcx、r8彩匕、r9等寄存器常用于存放函數(shù)參數(shù)
rsp腔剂、rbp用于棧操作

我們可以通過實例化類的方式來確認(rèn)一下上面所說的。
定義一個類驼仪,并實例化一個對象

image.png

進(jìn)入?yún)R編桶蝎,在SwiftStudy.Person.__allocating_init()處打上斷點
image.png

進(jìn)入到allocating_init
image.png

可以看到這里也是即將分配堆空間swift_allocObject,此時我們看第6行谅畅,通過rsi傳參時登渣,這里傳遞了32個字節(jié),前16個字節(jié)固定毡泻,后16為內(nèi)部變量胜茧。

其實我們可以將閉包看做是一個類對象,上面定義的exec可以看做是一個類對象仇味,其內(nèi)部函數(shù)捕獲的變量a就是其成員變量呻顽,而函數(shù)就是其內(nèi)部的函數(shù)。并且類對象就是分配在堆空間上的丹墨。甚至其在堆空間上的數(shù)據(jù)分配也是和類對象基本一致的廊遍,類的前16個字節(jié)存放類信息以及引用計數(shù),同樣閉包的堆空間的前16個字節(jié)也是存放著一些信息贩挣,真正的數(shù)據(jù)是從第17個字節(jié)開始喉前。

問題:前面我們分析的是局部變量,那如果是全局變量此時還會在堆是哪個分配對應(yīng)的空間嗎王财?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卵迂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绒净,更是在濱河造成了極大的恐慌见咒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挂疆,死亡現(xiàn)場離奇詭異改览,居然都是意外死亡,警方通過查閱死者的電腦和手機缤言,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門宝当,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人墨闲,你說我怎么就攤上這事今妄。” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵盾鳞,是天一觀的道長犬性。 經(jīng)常有香客問我,道長腾仅,這世上最難降的妖魔是什么乒裆? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮推励,結(jié)果婚禮上鹤耍,老公的妹妹穿的比我還像新娘。我一直安慰自己验辞,他們只是感情好稿黄,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著跌造,像睡著了一般杆怕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壳贪,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天陵珍,我揣著相機與錄音,去河邊找鬼违施。 笑死互纯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的磕蒲。 我是一名探鬼主播留潦,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亿卤!你這毒婦竟也來了愤兵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤排吴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后懦鼠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钻哩,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年肛冶,在試婚紗的時候發(fā)現(xiàn)自己被綠了街氢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡睦袖,死狀恐怖珊肃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤伦乔,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布厉亏,位于F島的核電站,受9級特大地震影響烈和,放射性物質(zhì)發(fā)生泄漏爱只。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一招刹、第九天 我趴在偏房一處隱蔽的房頂上張望恬试。 院中可真熱鬧,春花似錦疯暑、人聲如沸训柴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畦粮。三九已至,卻和暖如春乖阵,著一層夾襖步出監(jiān)牢的瞬間宣赔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工瞪浸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留儒将,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓对蒲,卻偏偏與公主長得像钩蚊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蹈矮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354