作者:Mike Ash弥喉,原文鏈接憋肖,原文日期:2016-01-29
譯者:ray16897188朝群;校對:Channe姜盈;定稿:千葉知風(fēng)
Swift 的類對大多數(shù)剛接觸編程語言的人來說是很容易理解的哭当,它們和其他語言中的類十分類似猪腕。無論你是從 Objective-C、Java 還是 Ruby 過來的钦勘,在 Swift 中對于類的使用并無太大區(qū)別陋葡。而 Swift 中的結(jié)構(gòu)體就是另外一回事兒了,它們有點(diǎn)兒像類彻采,但是它們是值類型腐缤,還沒有繼承,另外我總是聽到這個(gè)什么 copy-on-write(寫入時(shí)復(fù)制)的說法肛响。那么 Swift 中的結(jié)構(gòu)體是存在哪里岭粤?它們是怎么個(gè)工作原理?今天我們來仔細(xì)研究一下如何在內(nèi)存中保存和操作結(jié)構(gòu)體特笋。
簡單的結(jié)構(gòu)體
我建了一個(gè)有兩個(gè)文件的程序來探究一下結(jié)構(gòu)體在內(nèi)存中是怎樣存儲的剃浇。對這個(gè)測試程序采用 optimizations enabled 選項(xiàng)編譯,并取消 whole-module optimization 選項(xiàng)猎物。此測試是讓一個(gè)文件調(diào)用一個(gè)文件虎囚,這就會防止編譯器將所有東西都內(nèi)聯(lián),從而讓我們能更清楚的看明白東西都存在哪兒霸奕,以及數(shù)據(jù)在函數(shù)間如何傳遞溜宽。
從創(chuàng)建一個(gè)三個(gè)元素的結(jié)構(gòu)體開始:
struct ExampleInts {
var x: Int
var y: Int
var z: Int
}
又寫了三個(gè)函數(shù),都是各自接收一個(gè)結(jié)構(gòu)體的實(shí)例质帅,然后分別返回該實(shí)例的一個(gè)字段(field):
func getX(parameter: ExampleInts) -> Int {
return parameter.x
}
func getY(parameter: ExampleInts) -> Int {
return parameter.y
}
func getZ(parameter: ExampleInts) -> Int {
return parameter.z
}
在另一個(gè)文件中創(chuàng)建了一個(gè)結(jié)構(gòu)體實(shí)例,然后調(diào)用所有的 get 函數(shù):
func testGets() {
let s = ExampleInts(x: 1, y: 2, z: 3)
getX(s)
getY(s)
getZ(s)
}
針對 getX留攒,編譯器生成了如下代碼:
pushq %rbp
movq %rsp, %rbp
movq %rdi, %rax
popq %rbp
retq
查看一下匯編的備忘單煤惩,知道參數(shù)是按順序被傳進(jìn)寄存器 rdi、rsi炼邀、rdx魄揉、rcx、r8 和 r9 中拭宁,然后返回值被存放在 rax 中洛退。這里前兩個(gè)指令只是函數(shù)序言(function prologue)瓣俯,而后兩個(gè)是函數(shù)尾聲(function epilogue)。真正做的工作就是 movq %rdi, %rax
:提取第一個(gè)參數(shù)并將其返回兵怯。再看一下 getY:
pushq %rbp
movq %rsp, %rbp
movq %rsi, %rax
popq %rbp
retq
基本一樣彩匕,只不過它返回的是第二個(gè)參數(shù)。那 getZ 呢媒区?
pushq %rbp
movq %rsp, %rbp
movq %rdx, %rax
popq %rbp
retq
還是驼仪,基本都一樣,但返回的是第三個(gè)參數(shù)袜漩。從這我們可以看出來每個(gè)單獨(dú)的結(jié)構(gòu)體元素都是被看做獨(dú)立的參數(shù)绪爸,被單獨(dú)的傳遞進(jìn)函數(shù)中。在接收端挑出某個(gè)元素宙攻,僅僅就是選擇它所在的相應(yīng)的寄存器奠货。
在調(diào)用點(diǎn)驗(yàn)證一下。下面是 testGets 的編譯器生成碼:
pushq %rbp
movq %rsp, %rbp
movl $1, %edi
movl $2, %esi
movl $3, %edx
callq __TF4main4getXFVS_11ExampleIntsSi
movl $1, %edi
movl $2, %esi
movl $3, %edx
callq __TF4main4getYFVS_11ExampleIntsSi
movl $1, %edi
movl $2, %esi
movl $3, %edx
popq %rbp
jmp __TF4main4getZFVS_11ExampleIntsSi
可以看出這個(gè)結(jié)構(gòu)體實(shí)例的是直接在組建于參數(shù)寄存器上的座掘。(edi仇味、esi 和 edx 寄存器分別是 rdi、rsi 和 rdx 對應(yīng)的低 32 bit 帶寬版本雹顺。)這樣甚至不用在調(diào)用途中操心值的額外存儲丹墨,只需每次調(diào)用時(shí)重建這個(gè)結(jié)構(gòu)體的實(shí)例就好了。因?yàn)榫幾g器明確的知道寄存器中的內(nèi)容嬉愧,這就可以大大的改變 Swift 的代碼編寫方式贩挣。注意到對 getZ 的調(diào)用和對 getX、getY 的調(diào)用有些許不同:由于它是該函數(shù)中的最后一部分没酣,編譯器以尾調(diào)用(tail call)的形式將其生成王财,清空本地調(diào)用棧幀(local call frame),然后讓 getZ 直接返回到 testGets 函數(shù)被調(diào)用的地方裕便。
再讓我們看一下當(dāng)編譯器不知道結(jié)構(gòu)體的內(nèi)容時(shí)會生成怎樣的代碼绒净。下面是這個(gè) test 函數(shù)的變體,從其他的地方獲得結(jié)構(gòu)體的實(shí)例:
func testGets2() {
let s = getExampleInts()
getX(s)
getY(s)
getZ(s)
}
getExampleInts 創(chuàng)建了一個(gè)結(jié)構(gòu)體實(shí)例然后將其返回偿衰,但這個(gè)函數(shù)是在另一個(gè)文件中挂疆,所以優(yōu)化 testGets2 的時(shí)候編譯器是不知道發(fā)生了什么情況的。函數(shù)如下:
func getExampleInts() -> ExampleInts {
return ExampleInts(x: 1, y: 2, z: 3)
}
當(dāng)編譯器不知道結(jié)構(gòu)體的內(nèi)容時(shí) testGets2 會生成怎樣的代碼呢下翎?
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %rbx
pushq %rax
callq __TF4main14getExampleIntsFT_VS_11ExampleInts
movq %rax, %rbx
movq %rdx, %r14
movq %rcx, %r15
movq %rbx, %rdi
movq %r14, %rsi
movq %r15, %rdx
callq __TF4main4getXFVS_11ExampleIntsSi
movq %rbx, %rdi
movq %r14, %rsi
movq %r15, %rdx
callq __TF4main4getYFVS_11ExampleIntsSi
movq %rbx, %rdi
movq %r14, %rsi
movq %r15, %rdx
addq $8, %rsp
popq %rbx
popq %r14
popq %r15
popq %rbp
jmp __TF4main4getZFVS_11ExampleIntsSi
由于編譯器不能在每個(gè)階段都直接將相應(yīng)的值重現(xiàn)缤言,它就得把這些值存起來。結(jié)構(gòu)體的三個(gè)元素被放到 rbx视事、r14 和 r15 寄存器中胆萧,并在每次調(diào)用時(shí)從這些寄存器里將值加載到參數(shù)寄存器中。調(diào)用者會保存這三個(gè)寄存器俐东,就是說在調(diào)用過程中它們存的值會被持有跌穗。然后和之前一樣订晌,編譯器也對 getZ 生成了尾調(diào)用,以及一些更昂貴的預(yù)先清理蚌吸。
函數(shù)的開始部分調(diào)用了 getExampleInts 并從 rax锈拨、rdx 和 rcx 中加載了其中的值。顯然結(jié)構(gòu)體的值是從這些寄存器里返回的套利,看看 getExampleInts 函數(shù)來確認(rèn)下:
pushq %rbp
movl $1, %edi
movl $2, %esi
movl $3, %edx
popq %rbp
jmp __TFV4main11ExampleIntsCfMS0_FT1xSi1ySi1zSi_S0_
這代碼把值 1推励、2 和 3 放進(jìn)參數(shù)寄存器中,然后調(diào)用結(jié)構(gòu)體的構(gòu)造器肉迫。下面是構(gòu)造器的生成碼:
pushq %rbp
movq %rsp, %rbp
movq %rdx, %rcx
movq %rdi, %rax
movq %rsi, %rdx
popq %rbp
retq
夠清楚了验辞,它向 rax、rdx 和 rcx 中返回三個(gè)值喊衫。備忘單并未提及往多個(gè)寄存器中返回多個(gè)值跌造。那官方的PDF呢?里面說到了可以往 rax 和 rdx 中返回兩個(gè)值族购,卻沒說可以給 rcx 返回第三個(gè)值壳贪。而上面的代碼還是很明確的。新語言有趣的地方就在這兒寝杖,它不一定非按老規(guī)矩來违施。要是和C語言聯(lián)調(diào)就得按傳統(tǒng)規(guī)范,但是 Swift 和 Swift 之間的調(diào)用就可以玩新路子了瑟幕。
那 inout 參數(shù)呢磕蒲?如果它是像我們在 C 中所熟悉的那樣,結(jié)構(gòu)體就會被安置在內(nèi)存中只盹,然后傳過去一個(gè)指針辣往。下面是兩個(gè) test 函數(shù)(當(dāng)然是在兩個(gè)不同文件里的):
func testInout() {
var s = getExampleInts()
totalInout(&s)
}
func totalInout(inout parameter: ExampleInts) -> Int {
return parameter.x + parameter.y + parameter.z
}
下面是 testInout 的生成碼:
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
callq __TF4main14getExampleIntsFT_VS_11ExampleInts
movq %rax, -24(%rbp)
movq %rdx, -16(%rbp)
movq %rcx, -8(%rbp)
leaq -24(%rbp), %rdi
callq __TF4main10totalInoutFRVS_11ExampleIntsSi
addq $32, %rsp
popq %rbp
retq
函數(shù)序言中先創(chuàng)建了一個(gè) 32 字節(jié)的堆棧幀,再調(diào)用 getExampleInts殖卑,而后的調(diào)用把結(jié)果的值分別存在偏移量為 -24站削、-16 和 -8 的棧槽(stack slots)中。隨即計(jì)算出指向偏移為 -24 的指針孵稽,將其加載到 rdi 參數(shù)寄存器中后調(diào)用 totalInout许起。下面是這個(gè)函數(shù)的生成碼:
pushq %rbp
movq %rsp, %rbp
movq (%rdi), %rax
addq 8(%rdi), %rax
jo LBB4_3
addq 16(%rdi), %rax
jo LBB4_3
popq %rbp
retq
LBB4_3:
ud2
以上是從傳遞進(jìn)來的參數(shù)加載偏移量所對應(yīng)的值,合并之后將結(jié)果返回到 rax 中肛冶。jo 指令做溢出檢查街氢。如果有任一 addq 指令引起溢出,jo 指令會跳轉(zhuǎn)到 ud2 指令睦袖,將程序終結(jié)。
可以看出這正是我們所想的那樣:把一個(gè)結(jié)構(gòu)體傳遞給一個(gè) inout 參數(shù)時(shí)荣刑,該結(jié)構(gòu)體被置進(jìn)連續(xù)的內(nèi)存中馅笙,隨后得到一個(gè)指向該內(nèi)存的地址伦乔。
大結(jié)構(gòu)體
如果我們處理的是一些更大的結(jié)構(gòu)體,大到寄存器不再適合了的話會怎樣呢董习?下面是一個(gè)有十個(gè)元素的結(jié)構(gòu)體:
struct TenInts {
var elements = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
}
而這面是一個(gè) get 函數(shù)烈和,創(chuàng)建一該結(jié)構(gòu)體的實(shí)例并將其返回。為防止內(nèi)聯(lián)它被放在另一個(gè)文件中:
func getHuge() -> TenInts {
return TenInts()
}
一個(gè)獲取該結(jié)構(gòu)體中單個(gè)元素的函數(shù):
func getHugeElement(parameter: TenInts) -> Int {
return parameter.elements.5
}
最后是一個(gè) test 函數(shù):
func testHuge() {
let s = getHuge()
getHugeElement(s)
}
看下生成碼皿淋,從 testHuge 開始:
pushq %rbp
movq %rsp, %rbp
subq $160, %rsp
leaq -80(%rbp), %rdi
callq __TF4main7getHugeFT_VS_7TenInts
movups -80(%rbp), %xmm0
movups -64(%rbp), %xmm1
movups -48(%rbp), %xmm2
movups -32(%rbp), %xmm3
movups -16(%rbp), %xmm4
movups %xmm0, -160(%rbp)
movups %xmm1, -144(%rbp)
movups %xmm2, -128(%rbp)
movups %xmm3, -112(%rbp)
movups %xmm4, -96(%rbp)
leaq -160(%rbp), %rdi
callq __TF4main14getHugeElementFVS_7TenIntsSi
addq $160, %rsp
popq %rbp
retq
這段代碼(除去函數(shù)序言和尾聲)可以分成三部分招刹。
第一部分計(jì)算出對這個(gè)堆棧幀有 -80 偏移量的地址,然后調(diào)用 getHuge窝趣,把計(jì)算出的地址傳參給它疯暑。getHuge 函數(shù)在源代碼里沒有任何參數(shù),但是用一個(gè)隱式參數(shù)返回較大的結(jié)構(gòu)體并不罕見哑舒。調(diào)用處為返回值分配儲存空間妇拯,而后給把一個(gè)指向該分配好的空間的指針傳給隱藏參數(shù)。棧中的這塊已分配空間告訴我們基本就是這樣的洗鸵。
第二部分將棧偏移-80的地方結(jié)構(gòu)體復(fù)制到 -160 的地方越锈。它將這個(gè)結(jié)構(gòu)體加載到五個(gè) xmm 寄存器中,每次加載十六字節(jié)的片段膘滨,然后把寄存器的內(nèi)容放回到從 -160 開始的地方甘凭。我不大清楚為什么編譯器要弄一個(gè)拷貝而不是直接用原始值。我懷疑優(yōu)化器可能還是不夠聰明火邓,意識不到它根本就不需要用到拷貝丹弱。
第三部分計(jì)算出棧偏移 -160 的地址,然后調(diào)用 getHugeElement贡翘,傳參給它計(jì)算出的地址蹈矮。之前的三個(gè)元素的試驗(yàn)中傳遞的是寄存器中的值,而對于這個(gè)更大的結(jié)構(gòu)體鸣驱,傳遞的是指針泛鸟。
其他函數(shù)的生成碼確認(rèn)了這點(diǎn):結(jié)構(gòu)體是以指針形式傳進(jìn)傳出的,并存活在棧中踊东。從 getHugeElement 開始:
pushq %rbp
movq %rsp, %rbp
movq 40(%rdi), %rax
popq %rbp
retq
加載了離傳入?yún)?shù) 40 個(gè)偏移量的內(nèi)容北滥。每個(gè)元素為 8 字節(jié),偏移量是 40 就是第 5 個(gè)元素闸翅,該函數(shù)返回這個(gè)值再芋。
getHuge 函數(shù):
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq $88, %rsp
movq %rdi, %rbx
leaq -88(%rbp), %rdi
callq __TFV4main7TenIntsCfMS0_FT_S0_
movups -88(%rbp), %xmm0
movups -72(%rbp), %xmm1
movups -56(%rbp), %xmm2
movups -40(%rbp), %xmm3
movups -24(%rbp), %xmm4
movups %xmm0, (%rbx)
movups %xmm1, 16(%rbx)
movups %xmm2, 32(%rbx)
movups %xmm3, 48(%rbx)
movups %xmm4, 64(%rbx)
movq %rbx, %rax
addq $88, %rsp
popq %rbx
popq %rbp
retq
和上面的 testHuge 很像:分配棧空間坚冀,調(diào)用一個(gè)函數(shù)济赎,這回是 TenInts 構(gòu)造器函數(shù),然后把返回值復(fù)制到它最終的地方:隱式參數(shù)傳進(jìn)來的指針?biāo)赶虻牡刂贰?/p>
都說到這兒了,看一下 TenInts 構(gòu)造器吧:
pushq %rbp
movq %rsp, %rbp
movq $1, (%rdi)
movq $2, 8(%rdi)
movq $3, 16(%rdi)
movq $4, 24(%rdi)
movq $5, 32(%rdi)
movq $6, 40(%rdi)
movq $7, 48(%rdi)
movq $8, 56(%rdi)
movq $9, 64(%rdi)
movq $10, 72(%rdi)
movq %rdi, %rax
popq %rbp
retq
類似于另一個(gè)函數(shù)司训,它也是用了一個(gè)指向新結(jié)構(gòu)體的隱式指針作為參數(shù)构捡,然后把從 1 到 10 這些值存儲好,再返回壳猜。
創(chuàng)建這些test案例時(shí)我遇到過一個(gè)有意思的地方勾徽。這是一個(gè) test 函數(shù),調(diào)用了三次 getHugeElement:
func testThreeHuge() {
let s = getHuge()
getHugeElement(s)
getHugeElement(s)
getHugeElement(s)
}
其生成碼如下:
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
subq $392, %rsp
leaq -120(%rbp), %rdi
callq __TF4main7getHugeFT_VS_7TenInts
movq -120(%rbp), %rbx
movq %rbx, -376(%rbp)
movq -112(%rbp), %r8
movq %r8, -384(%rbp)
movq -104(%rbp), %r9
movq %r9, -392(%rbp)
movq -96(%rbp), %r10
movq %r10, -400(%rbp)
movq -88(%rbp), %r11
movq %r11, -368(%rbp)
movq -80(%rbp), %rax
movq -72(%rbp), %rcx
movq %rcx, -408(%rbp)
movq -64(%rbp), %rdx
movq %rdx, -416(%rbp)
movq -56(%rbp), %rsi
movq %rsi, -424(%rbp)
movq -48(%rbp), %rdi
movq %rdi, -432(%rbp)
movq %rbx, -200(%rbp)
movq %rbx, %r14
movq %r8, -192(%rbp)
movq %r8, %r15
movq %r9, -184(%rbp)
movq %r9, %r12
movq %r10, -176(%rbp)
movq %r10, %r13
movq %r11, -168(%rbp)
movq %rax, -160(%rbp)
movq %rax, %rbx
movq %rcx, -152(%rbp)
movq %rdx, -144(%rbp)
movq %rsi, -136(%rbp)
movq %rdi, -128(%rbp)
leaq -200(%rbp), %rdi
callq __TF4main14getHugeElementFVS_7TenIntsSi
movq %r14, -280(%rbp)
movq %r15, -272(%rbp)
movq %r12, -264(%rbp)
movq %r13, -256(%rbp)
movq -368(%rbp), %rax
movq %rax, -248(%rbp)
movq %rbx, -240(%rbp)
movq -408(%rbp), %r14
movq %r14, -232(%rbp)
movq -416(%rbp), %r15
movq %r15, -224(%rbp)
movq -424(%rbp), %r12
movq %r12, -216(%rbp)
movq -432(%rbp), %r13
movq %r13, -208(%rbp)
leaq -280(%rbp), %rdi
callq __TF4main14getHugeElementFVS_7TenIntsSi
movq -376(%rbp), %rax
movq %rax, -360(%rbp)
movq -384(%rbp), %rax
movq %rax, -352(%rbp)
movq -392(%rbp), %rax
movq %rax, -344(%rbp)
movq -400(%rbp), %rax
movq %rax, -336(%rbp)
movq -368(%rbp), %rax
movq %rax, -328(%rbp)
movq %rbx, -320(%rbp)
movq %r14, -312(%rbp)
movq %r15, -304(%rbp)
movq %r12, -296(%rbp)
movq %r13, -288(%rbp)
leaq -360(%rbp), %rdi
callq __TF4main14getHugeElementFVS_7TenIntsSi
addq $392, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq
此函數(shù)的結(jié)構(gòu)和前面的那個(gè)版本類似统扳,調(diào)用 getHuge喘帚,復(fù)制結(jié)果,然后調(diào)用三次 getHugeElement咒钟。每次的調(diào)用都再次的復(fù)制該結(jié)構(gòu)體吹由,猜測是為了防止 getHugeElement 發(fā)生變動(dòng)。發(fā)現(xiàn)真正有意思的是這些都是使用整型寄存器盯腌、每次只復(fù)制一個(gè)元素溉知,而不是像 testHuge 函數(shù)那樣每次往 xmm 寄存器中復(fù)制兩個(gè)元素。我不確定是什么導(dǎo)致編譯器在這里選擇了整型寄存器腕够,看起來用 xmm 寄存器一次復(fù)制兩個(gè)元素是更有效率的级乍,生成碼也更簡潔。
我還試驗(yàn)了非常大的結(jié)構(gòu)體:
struct HundredInts {
var elements = (TenInts(), TenInts(), TenInts(), TenInts(), TenInts(), TenInts(), TenInts(), TenInts(), TenInts(), TenInts())
}
struct ThousandInts {
var elements = (HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts(), HundredInts())
}
func getThousandInts() -> ThousandInts {
return ThousandInts()
}
getThousandInts 的生成碼相當(dāng)?shù)寞偪瘢?/p>
pushq %rbp
pushq %rbx
subq $8008, %rsp
movq %rdi, %rbx
leaq -8008(%rbp), %rdi
callq __TFV4main12ThousandIntsCfMS0_FT_S0_
movq -8008(%rbp), %rax
movq %rax, (%rbx)
movq -8000(%rbp), %rax
movq %rax, 8(%rbx)
movq -7992(%rbp), %rax
movq %rax, 16(%rbx)
movq -7984(%rbp), %rax
movq %rax, 24(%rbx)
movq -7976(%rbp), %rax
movq %rax, 32(%rbx)
movq -7968(%rbp), %rax
movq %rax, 40(%rbx)
movq -7960(%rbp), %rax
movq %rax, 48(%rbx)
movq -7952(%rbp), %rax
movq %rax, 56(%rbx)
movq -7944(%rbp), %rax
movq %rax, 64(%rbx)
movq -7936(%rbp), %rax
movq %rax, 72(%rbx)
...
movq -104(%rbp), %rax
movq %rax, 7904(%rbx)
movq -96(%rbp), %rax
movq %rax, 7912(%rbx)
movq -88(%rbp), %rax
movups -80(%rbp), %xmm0
movups -64(%rbp), %xmm1
movups -48(%rbp), %xmm2
movups -32(%rbp), %xmm3
movq %rax, 7920(%rbx)
movq -16(%rbp), %rax
movups %xmm0, 7928(%rbx)
movups %xmm1, 7944(%rbx)
movups %xmm2, 7960(%rbx)
movups %xmm3, 7976(%rbx)
movq %rax, 7992(%rbx)
movq %rbx, %rax
addq $8008, %rsp
popq %rbx
popq %rbp
retq
編譯器為復(fù)制這個(gè)結(jié)構(gòu)體生成了兩千多條指令帚湘。這種情況下貌似調(diào)用 memcpy 函數(shù)非常合適玫荣,而我覺得為這種大的出奇的結(jié)構(gòu)體做優(yōu)化應(yīng)該不是編譯器團(tuán)隊(duì)現(xiàn)在的首要目標(biāo)。
類字段(Class Fields)
我們來看看當(dāng)結(jié)構(gòu)體的字段(struct fields)比整形復(fù)雜的多的情況下會發(fā)生什么大诸。下面有個(gè)簡單的類捅厂,然后包含了一個(gè)結(jié)構(gòu)體:
class ExampleClass {}
struct ContainsClass {
var x: Int
var y: ExampleClass
var z: Int
}
這里是一堆試驗(yàn)的函數(shù)(分在兩個(gè)不同文件中防止內(nèi)聯(lián)):
func testContainsClass() {
let s = ContainsClass(x: 1, y: getExampleClass(), z: 3)
getClassX(s)
getClassY(s)
getClassZ(s)
}
func getExampleClass() -> ExampleClass {
return ExampleClass()
}
func getClassX(parameter: ContainsClass) -> Int {
return parameter.x
}
func getClassY(parameter: ContainsClass) -> ExampleClass {
return parameter.y
}
func getClassZ(parameter: ContainsClass) -> Int {
return parameter.z
}
從 getters 的生成碼看起,首先是 getClassX:
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %rax
movq %rdi, %rbx
movq %rsi, %rdi
callq _swift_release
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %rbp
retq
三個(gè)結(jié)構(gòu)體元素會被傳遞進(jìn)前三個(gè)參數(shù)寄存器中资柔,rdi焙贷、rsi 和 rdx。該函數(shù)想通過把 rdi 中的值移動(dòng)到 rax 中再返回贿堰,但得先記錄一下才行辙芍。看上去似乎是傳入 rsi 的對象引用被持有了羹与,在函數(shù)返回之前是必須被釋放掉的故硅。這段生成碼把 rdi 搬進(jìn)了一個(gè)安全的臨時(shí)寄存器 rbx,然后將對象引用移動(dòng)到 rdi纵搁,再調(diào)用 swift_release 將其釋放吃衅。隨后把 rbx 中的值移動(dòng)到 rax 中,再從函數(shù)中返回腾誉。
getClassZ 也很類似徘层,除了它是從 rdx峻呕,而不是 rdi 中獲取值的:
pushq %rbp
movq %rsp, %rbp
pushq %rbx
pushq %rax
movq %rdx, %rbx
movq %rsi, %rdi
callq _swift_release
movq %rbx, %rax
addq $8, %rsp
popq %rbx
popq %rbp
retq
getClassY 的生成碼會是特殊的一個(gè),因?yàn)樗祷氐氖菍ο蟮囊枚且粋€(gè)整型:
pushq %rbp
movq %rsp, %rbp
movq %rsi, %rax
popq %rbp
retq
十分簡短惑灵!它從 rsi 中取值山上,該值為對象引用眼耀,然后放進(jìn) rax 再將其返回英支。這里就不需要記錄,僅僅是數(shù)據(jù)的拖拽哮伟。顯然值傳進(jìn)來的時(shí)候被持有干花,被返回的時(shí)候也被持有,所以這段代碼是無需任何內(nèi)存管理的楞黄。
到目前為止我們看到的對這個(gè)結(jié)構(gòu)體的處理和前面的對那個(gè)有三個(gè)整型元素的結(jié)構(gòu)體的處理沒有太大區(qū)別池凄,除了對象引用字段傳遞進(jìn)來是被持有的,必須要由被調(diào)用者做釋放處理鬼廓。記著這一點(diǎn)肿仑,我們來看下 testContainsClass 的生成碼:
pushq %rbp
movq %rsp, %rbp
pushq %r14
pushq %rbx
callq __TF4main15getExampleClassFT_CS_12ExampleClass
movq %rax, %rbx
movq %rbx, %rdi
callq _swift_retain
movq %rax, %r14
movl $1, %edi
movl $3, %edx
movq %rbx, %rsi
callq __TF4main9getClassXFVS_13ContainsClassSi
movq %r14, %rdi
callq _swift_retain
movl $1, %edi
movl $3, %edx
movq %rbx, %rsi
callq __TF4main9getClassYFVS_13ContainsClassCS_12ExampleClass
movq %rax, %rdi
callq _swift_release
movl $1, %edi
movl $3, %edx
movq %rbx, %rsi
popq %rbx
popq %r14
popq %rbp
jmp __TF4main9getClassZFVS_13ContainsClassSi
這個(gè)函數(shù)做的第一件事就是調(diào)用 getExampleClass 來獲得結(jié)構(gòu)體中存儲的 ExampleClass 實(shí)例,它得到返回的引用之后將其移動(dòng)到 rbx 中以安全保留碎税。
接下來調(diào)用了 getClassX尤慰,為此它得在參數(shù)寄存器中建立一個(gè)該結(jié)構(gòu)體的拷貝。兩個(gè)整型的字段是很容易的雷蹂,而對象的字段就需要按照函數(shù)所期的那樣被持有伟端。這段代碼對 rbx 中所存的值調(diào)用了 swift_retain,然后將其放入 rsi匪煌,再把 1 和 3 分別放入 rdi 和 rdx 中责蝠,構(gòu)建出完整的結(jié)構(gòu)體。最后萎庭,它調(diào)用了 getClassX霜医。
調(diào)用 getClassY 也基本一樣,然而 getClassY 返回的是一個(gè)需要被釋放的對象驳规,在調(diào)用之后這段代碼將返回值移動(dòng)至 rdi 中肴敛,調(diào)用 swift_release 來實(shí)線所要求的內(nèi)存管理。
函數(shù)調(diào)用 getClassZ 作為其尾調(diào)用达舒,所以這里的生成代碼有些許不同值朋。從 getExampleClass 得來的對象引用已被持有,所以它不需要為這個(gè)最后的調(diào)用而再被單獨(dú)持有巩搏。代碼將其放進(jìn) rsi 里昨登,再把 1 和 3 分別放進(jìn) rdi 和 rdx 里,然后做清空棧贯底,跳轉(zhuǎn)到 getClassZ 做最后的調(diào)用丰辣。
基本上說撒强,與全是整型的那個(gè)結(jié)構(gòu)體相比幾乎沒有變化。唯一的實(shí)質(zhì)的不同就是復(fù)制一個(gè)帶有對象的結(jié)構(gòu)體時(shí)需要持有這個(gè)對象笙什,銷毀這個(gè)結(jié)構(gòu)體時(shí)也需要釋放掉這個(gè)對象飘哨。
結(jié)論
Swift 中的結(jié)構(gòu)體存儲根本上講還是比較簡單,我們看到的這些也是從C語言中非常簡單的結(jié)構(gòu)體中延續(xù)過來的琐凭。一個(gè)結(jié)構(gòu)體實(shí)例在很大程度上可以看做是一些獨(dú)立值的松散集合芽隆,需要時(shí)這些值可以作為整體被操作。本地的結(jié)構(gòu)體變量可能會被存儲到棧中统屈,其中的每個(gè)元素可能會被存到寄存器里胚吁,這取決于結(jié)構(gòu)體的大小、寄存器對余下代碼的利用以及編譯器臨時(shí)冒出的什么點(diǎn)子愁憔。小的結(jié)構(gòu)體在寄存器中傳遞和返回腕扶,大的結(jié)構(gòu)體通過引用傳遞和返回。結(jié)構(gòu)體在傳遞和返回時(shí)會被復(fù)制吨掌,盡管你可以用結(jié)構(gòu)體來實(shí)現(xiàn)寫入時(shí)復(fù)制(copy-on-write)的數(shù)據(jù)類型半抱,但是基本的語言框架還是會被復(fù)制,而且在選擇復(fù)制的數(shù)據(jù)時(shí)多多少少有些盲目膜宋。
今天就到這兒吧窿侈。歡迎再回來閱讀更多的精彩編程技術(shù)。Friday Q&A 是由讀者想法驅(qū)動(dòng)的激蹲,所以如果你等不及下一期棉磨,還有一些希望看到的討論話題,就發(fā)郵件過來吧学辱!
覺得這篇文章怎么樣乘瓤?我在買一本書,里面全是這樣的文章策泣。在 iBooks 上和 Kindle 上有售衙傀,加上一個(gè)可以直接下載的PDF和ePub格式。還有傳統(tǒng)的紙質(zhì)版本萨咕。點(diǎn)擊這里查看更多信息
本文由 SwiftGG 翻譯組翻譯统抬,已經(jīng)獲得作者翻譯授權(quán),最新文章請?jiān)L問 http://swift.gg危队。