摘要:一般情況下程序計(jì)數(shù)器中的值總是按照程序指令順序更新,只有在執(zhí)行跳轉(zhuǎn)指令和函數(shù)調(diào)用指令時(shí)才會(huì)打破執(zhí)行的順序。不同的體系都提供了特定的函數(shù)調(diào)用指令來(lái)實(shí)現(xiàn)函數(shù)調(diào)用的功能。位體系下的函數(shù)調(diào)用規(guī)則函數(shù)的調(diào)用函數(shù)調(diào)
古器合尺度,法物應(yīng)矩規(guī)。--蘇洵
一、什么是函數(shù)
可執(zhí)行程序是為了實(shí)現(xiàn)某個(gè)功能而由不同機(jī)器指令按特定規(guī)則進(jìn)行組合排列的集合。無(wú)論高級(jí)還是低級(jí)程序語(yǔ)言,無(wú)論是面向?qū)ο筮€是面向過(guò)程的語(yǔ)言最終的代碼都會(huì)轉(zhuǎn)化為一條條機(jī)器指令的形式被執(zhí)行。為了管理上的方便和對(duì)代碼的復(fù)用,往往需要將某一段實(shí)現(xiàn)特定功能的指令集合進(jìn)行抽離和處理從而形成了函數(shù)的概念,函數(shù)也可以稱之為子程序或者子例程。出現(xiàn)函數(shù)的概念后可執(zhí)行程序的機(jī)器指令集合將不再是單一的一塊代碼,而是由多個(gè)函數(shù)組成的分塊代碼,這樣可執(zhí)行程序就變成了由函數(shù)之間相互調(diào)用這種方式來(lái)構(gòu)建和組織了。
一個(gè)函數(shù)由函數(shù)簽名、參數(shù)、返回、實(shí)現(xiàn)四部分組成。函數(shù)的前三者定義了明確的邊界信息,也稱之為函數(shù)接口描述。函數(shù)接口描述的意義在于調(diào)用者不再需要了解被調(diào)用者函數(shù)的實(shí)現(xiàn)細(xì)節(jié),而只需要按被調(diào)用者的定義的接口進(jìn)行交互即可。如何去定義一個(gè)函數(shù),如何去實(shí)現(xiàn)一個(gè)函數(shù),如何去調(diào)用一個(gè)函數(shù),如何將參數(shù)傳遞給被調(diào)用的函數(shù),如何使用被調(diào)用者函數(shù)的返回這些都需要有統(tǒng)一的標(biāo)準(zhǔn)規(guī)范來(lái)進(jìn)行界定,這個(gè)規(guī)則有兩個(gè)層面的標(biāo)準(zhǔn):在高級(jí)語(yǔ)言層面的規(guī)則稱之為API規(guī)則;而在機(jī)器指令層面上則由于不同的操作系統(tǒng)以及不同的CPU體系結(jié)構(gòu)下提供的指令集和構(gòu)造程序的方式不同而不同,所以在系統(tǒng)層面的規(guī)則稱之為ABI規(guī)則。本文的重點(diǎn)是詳細(xì)介紹函數(shù)調(diào)用、函數(shù)參數(shù)傳遞、函數(shù)返回值這3個(gè)方面的ABI規(guī)則,通過(guò)對(duì)這些規(guī)則的詳細(xì)介紹相信您對(duì)什么是函數(shù)就會(huì)有更加深入的了解。需要注意的是這里的ABI規(guī)則是指基于OC語(yǔ)言實(shí)現(xiàn)的程序的ABI規(guī)則,這些規(guī)則并不適用于通過(guò)Swift實(shí)現(xiàn)的程序以及不適用于Linux等其他操作系統(tǒng)的ABI規(guī)則。
由于內(nèi)容過(guò)多因此我將分為兩篇文章來(lái)做具體介紹,前一篇文章介紹函數(shù)接口相關(guān)的內(nèi)容,后一篇文章介紹函數(shù)實(shí)現(xiàn)相關(guān)的內(nèi)容。
二、函數(shù)調(diào)用
CPU中的程序計(jì)數(shù)器(IP/PC)中總是保存著下一條將要執(zhí)行的指令的內(nèi)存地址,這樣每執(zhí)行一條指令就會(huì)更新程序計(jì)數(shù)器中的值,從而可以繼續(xù)執(zhí)行下一條指令。系統(tǒng)就是這樣通過(guò)不停的變化程序計(jì)數(shù)器中的值來(lái)實(shí)現(xiàn)程序指令的執(zhí)行的。一般情況下程序計(jì)數(shù)器中的值總是按照程序指令順序更新,只有在執(zhí)行跳轉(zhuǎn)指令和函數(shù)調(diào)用指令時(shí)才會(huì)打破執(zhí)行的順序。
函數(shù)調(diào)用的本質(zhì)就是將函數(shù)在內(nèi)存中的首地址賦值給程序計(jì)數(shù)器(IP/PC),這樣下一條執(zhí)行的指令就變?yōu)榱撕瘮?shù)首地址處的指令,從而實(shí)現(xiàn)函數(shù)的調(diào)用。除了要更新程序計(jì)數(shù)器的值外還需要保存調(diào)用現(xiàn)場(chǎng),以便當(dāng)函數(shù)調(diào)用返回后繼續(xù)執(zhí)行函數(shù)調(diào)用的下一條指令,所以這里所謂的保存調(diào)用現(xiàn)場(chǎng)就是將函數(shù)調(diào)用的下一條指令的地址保存起來(lái)。不同的CPU體系都提供了特定的函數(shù)調(diào)用指令來(lái)實(shí)現(xiàn)函數(shù)調(diào)用的功能。比如x86系統(tǒng)提供一條稱之為call的指令來(lái)實(shí)現(xiàn)函數(shù)調(diào)用,call指令除了會(huì)更新程序計(jì)數(shù)器的值外還會(huì)把函數(shù)調(diào)用的下一條指令壓入到棧中進(jìn)行保存;arm系統(tǒng)則提供b系列的指令來(lái)實(shí)現(xiàn)函數(shù)調(diào)用,b系列指令除了會(huì)更新程序計(jì)數(shù)器的值外還會(huì)把函數(shù)調(diào)用的下一條指令保存到LR寄存器中。
函數(shù)返回的本質(zhì)就是將前面說(shuō)到的保存的調(diào)用現(xiàn)場(chǎng)地址賦值給程序計(jì)數(shù)器,這樣下一條執(zhí)行的指令就變?yōu)榱苏{(diào)用者調(diào)用被調(diào)函數(shù)的下一條指令了。不同的CPU體系也都提供了特定的函數(shù)返回指令來(lái)實(shí)現(xiàn)函數(shù)返回的功能(arm32位系統(tǒng)除外)。比如x86系統(tǒng)提供一條稱之為ret的指令來(lái)實(shí)現(xiàn)函數(shù)返回,此指令會(huì)將棧頂保存的地址賦值給程序計(jì)數(shù)器然后執(zhí)行出棧操作;arm64位系統(tǒng)也提供一條ret指令來(lái)實(shí)現(xiàn)函數(shù)的返回,此指令則會(huì)把當(dāng)前的LR寄存器的值賦值給程序計(jì)數(shù)器。
對(duì)于x86系統(tǒng)來(lái)說(shuō)因?yàn)閳?zhí)行函數(shù)調(diào)用前會(huì)將調(diào)用者的下一條指令壓入棧中,而被調(diào)用者函數(shù)內(nèi)部因?yàn)橛斜镜貤?stack frame)的定義又會(huì)將棧頂下移,所以在被調(diào)用者函數(shù)執(zhí)行ret指令返回之前需要確保當(dāng)前堆棧寄存器SP所指向的棧頂?shù)刂芬捅徽{(diào)用函數(shù)執(zhí)行前的棧頂?shù)刂繁3忠恢?,不然?dāng)ret指令執(zhí)行時(shí)取出的調(diào)用者的下一條指令的值將是錯(cuò)誤的,從而會(huì)產(chǎn)生崩潰異常。
對(duì)于arm系統(tǒng)來(lái)說(shuō)因?yàn)長(zhǎng)R寄存器只有一個(gè),因此如果被調(diào)用函數(shù)內(nèi)部也調(diào)用其他函數(shù)時(shí)也會(huì)更新LR寄存器的值,一旦LR寄存器被更新后將無(wú)法恢復(fù)正確的調(diào)用現(xiàn)場(chǎng),所以一般情況下被調(diào)用函數(shù)的前幾條指令做的事情就是將LR寄存器的值保存到棧內(nèi)存中,而被調(diào)用函數(shù)的最后幾條指令所的事情就是將棧內(nèi)存中保存的內(nèi)容恢復(fù)到LR寄存器。
有一種特殊的函數(shù)調(diào)用場(chǎng)景就是當(dāng)函數(shù)調(diào)用發(fā)生在調(diào)用者函數(shù)的最后一條指令時(shí),則不需要進(jìn)行調(diào)用現(xiàn)場(chǎng)的保護(hù)處理,同時(shí)也會(huì)將函數(shù)調(diào)用指令改為跳轉(zhuǎn)指令,原因是因?yàn)檎{(diào)用者的最后一條指令再無(wú)下一條有效的指令,而仍然采用調(diào)用指令的話則保存的調(diào)用現(xiàn)場(chǎng)則是個(gè)無(wú)效的地址,這樣當(dāng)函數(shù)返回時(shí)將跳轉(zhuǎn)到這個(gè)無(wú)效的地址從而產(chǎn)生執(zhí)行異常!
為了更好的描述函數(shù)的調(diào)用規(guī)則,假設(shè)A函數(shù)內(nèi)部調(diào)用了B函數(shù)和C函數(shù),下面定義了各函數(shù)的地址,以及函數(shù)調(diào)用處的地址,以及函數(shù)調(diào)用的偽代碼塊:
//這里的XX,YY,ZZ代表的是函數(shù)指令在內(nèi)存中的地址。 A XX1: XX2: 調(diào)用B函數(shù)地址YY1 XX3: XX4: XXn: 跳轉(zhuǎn)到C函數(shù)ZZ1 B YY1: YY2: YY3: YYn: 返回 C ZZ1: ZZ2: ZZ3: ZZn: 返回
1. x86_64體系下的函數(shù)調(diào)用規(guī)則
1.1 函數(shù)的調(diào)用
函數(shù)調(diào)用的指令是call 指令。在匯編語(yǔ)言中call 指令后面的操作數(shù)是調(diào)用的目標(biāo)函數(shù)的絕對(duì)地址,而實(shí)際的機(jī)器指令中的操作數(shù)則是一個(gè)相對(duì)地址值,這個(gè)地址值是目標(biāo)函數(shù)地址距離當(dāng)前指令地址的相對(duì)偏移值。無(wú)論是x86系統(tǒng)還是arm系統(tǒng)如果指令中的操作數(shù)部分的值是內(nèi)存地址的話,一般都是相對(duì)當(dāng)前指令的偏移地址而不是絕對(duì)地址。下面就是函數(shù)調(diào)用指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
call YY1 <==> RIP = YY1, RSP = RSP-8, *RSP = XX3
也就是說(shuō)執(zhí)行一條函數(shù)調(diào)用指令等價(jià)于將指令中的地址賦值給IP寄存器,同時(shí)把函數(shù)的返回地址壓入棧寄存器中去。
1.2 函數(shù)的跳轉(zhuǎn)
函數(shù)跳轉(zhuǎn)的指令是jmp指令。在匯編語(yǔ)言中jmp 指令后面的操作數(shù)是調(diào)用的目標(biāo)函數(shù)的絕對(duì)地址,而實(shí)際的機(jī)器指令中的操作數(shù)則是一個(gè)相對(duì)地址值,這個(gè)地址值是目標(biāo)函數(shù)地址距離當(dāng)前指令地址的相對(duì)偏移值,下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
jmp ZZ1 <==> RIP = ZZ1
也就是說(shuō)執(zhí)行一條跳轉(zhuǎn)指令等價(jià)于將指令中的地址賦值給IP寄存器。
1.3 函數(shù)的返回
函數(shù)返回的指令是ret指令。ret指令后面一般不跟操作數(shù),下面就是函數(shù)返回指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
ret <==> RIP = *RSP, RSP = RSP + 8
也就是說(shuō)執(zhí)行一條ret指令等價(jià)于將當(dāng)前棧寄存器中的值賦值給IP寄存器,同時(shí)棧寄存器執(zhí)行POP操作。
2. arm32位體系下的函數(shù)調(diào)用規(guī)則
2.1 函數(shù)的調(diào)用
函數(shù)的調(diào)用指令為bl/blx。 這兩條指令的操作數(shù)可以是相對(duì)地址偏移也可以是寄存器。bl/blx的區(qū)別就是bl函數(shù)調(diào)用不會(huì)切換指令集,而blx調(diào)用則會(huì)從thumb指令集切換到arm指令集或者相反切換。arm32系統(tǒng)中存在著兩套指令集即thumb指令集和arm指令集,其中的arm指令集中的所有的指令的長(zhǎng)度都是32位而thumb指令集則存在著32位和16位兩種長(zhǎng)度的指令集。兩種指令集是以函數(shù)為單位進(jìn)行使用的,也就是說(shuō)一個(gè)函數(shù)中的所有指令要么都是arm指令要么就都是thumb指令。正是因?yàn)槿绱巳绻{(diào)用者函數(shù)和被調(diào)用者函數(shù)之間用的是不同的指令集則需要通過(guò)blx來(lái)執(zhí)行函數(shù)調(diào)用,而如果二者所用的指令集相同則需要通過(guò)bl指令來(lái)執(zhí)行調(diào)用。下面就是函數(shù)調(diào)用指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
bl/blx YY1 <==> PC = YY1, LR = XX3
也就是說(shuō)執(zhí)行一條函數(shù)調(diào)用指令等價(jià)于將指令中的地址賦值給PC寄存器,同時(shí)把函數(shù)的返回地址賦值給LR寄存器中去。
2.2 函數(shù)的跳轉(zhuǎn)
函數(shù)的跳轉(zhuǎn)指令是b/bx, 這兩條指令的操作數(shù)可以是相對(duì)地址偏移也可以是寄存器,b/bx的區(qū)別就是b函數(shù)調(diào)用不會(huì)切換指令集。下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
b/bx ZZ1 <==> PC = ZZ1
也就是說(shuō)跳轉(zhuǎn)指令等價(jià)于將指令中的地址賦值給PC寄存器。
2.3 函數(shù)的返回
arm32位系統(tǒng)沒(méi)有專門的函數(shù)返回ret指令,因?yàn)閍rm32位系統(tǒng)可以直接修改PC寄存器的值,所以函數(shù)返回可以直接給PC指令賦值,也可以通過(guò)調(diào)用b/bx LR 來(lái)實(shí)現(xiàn)函數(shù)的返回處理。
b/bx LR //或者 mov PC, XXX
arm32位系統(tǒng)可以直接修改PC寄存器的值,因此函數(shù)返回時(shí)可以直接設(shè)置PC寄存器的值為函數(shù)的返回地址,也可以執(zhí)行b/bx跳轉(zhuǎn)指令并指定目標(biāo)地址為L(zhǎng)R寄存器中的值。
3.arm64位體系下的函數(shù)調(diào)用規(guī)則
3.1 函數(shù)的調(diào)用
函數(shù)調(diào)用的指令是bl/blr 其中bl指令的操作數(shù)是距離當(dāng)前位置相對(duì)距離的偏移地址,blr指令的操作數(shù)則是寄存器,表明調(diào)用寄存器所指定的地址。因?yàn)閎l指令中的操作數(shù)部分是函數(shù)的相對(duì)偏移地址,又因?yàn)閍rm64位系統(tǒng)的一條指令占用4個(gè)字節(jié),根據(jù)指令的定義bl指令所能跳轉(zhuǎn)的范圍是距離當(dāng)前位置±32MB的范圍,所以如果要跳轉(zhuǎn)到更遠(yuǎn)的地址則需要借助blr指令。 下面就是函數(shù)調(diào)用指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
//如果YY1地址離調(diào)用指令的距離是在±32MB內(nèi)則使用bl指令即可。 bl YY1 <==> PC = YY1, LR = XX3 //如果YY1地址離調(diào)用指令的距離超過(guò)±32MB則使用blr指令執(zhí)行間接調(diào)用。 ldr x16, YY1 blr x16
也就是說(shuō)執(zhí)行一條函數(shù)調(diào)用指令等價(jià)于將指令中的地址賦值給PC寄存器,同時(shí)把函數(shù)的返回地址賦值給LR寄存器中去。
3.2函數(shù)的跳轉(zhuǎn)
函數(shù)跳轉(zhuǎn)的指令是b/br, 其中b指令的操作數(shù)是距離當(dāng)前位置相對(duì)距離的偏移地址,br指令的操作數(shù)則是寄存器,表明跳轉(zhuǎn)到寄存器所指定的地址中去。下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
b ZZ1 <==> PC = ZZ1
也就是說(shuō)跳轉(zhuǎn)指令等價(jià)于將指令中的地址賦值給PC寄存器。
3.3 函數(shù)的返回
函數(shù)返回的指令是 ret, 下面就是函數(shù)返回指令以及其內(nèi)部實(shí)現(xiàn)的等價(jià)操作。
ret <==> PC = LR
也就是說(shuō)執(zhí)行一條ret指令等價(jià)于將LR寄存器中的值賦值給PC寄存器。
三、函數(shù)參數(shù)傳遞
某些函數(shù)定義中有參數(shù)需要傳遞,需要由調(diào)用者函數(shù)將參數(shù)傳遞給被調(diào)用者函數(shù),因此在調(diào)用這類函數(shù)時(shí),需要在執(zhí)行函數(shù)調(diào)用指令之前,進(jìn)行函數(shù)參數(shù)的傳遞。函數(shù)的參數(shù)個(gè)數(shù)可以為0個(gè),也可以為某個(gè)固定的數(shù)量,也可以為任意數(shù)量(可變參數(shù))。 函數(shù)的每個(gè)參數(shù)類型可以是整型數(shù)據(jù)類型,也可以是浮點(diǎn)數(shù)據(jù)類型,也可以是指針,也可以是結(jié)構(gòu)體。因此在函數(shù)傳遞的規(guī)則上需要明確指出調(diào)用者應(yīng)該如何將參數(shù)進(jìn)行保存處理,而被調(diào)用者又是從什么地方來(lái)獲取這些外部傳遞進(jìn)來(lái)的參數(shù)值。不同體系下的系統(tǒng)會(huì)根據(jù)參數(shù)定義的個(gè)數(shù)和類型來(lái)制定不同的規(guī)則。一般情況下各系統(tǒng)都會(huì)約定一些特定的寄存器來(lái)進(jìn)行參數(shù)傳遞交換,或者使用棧內(nèi)存來(lái)進(jìn)行參數(shù)傳遞交換。
1. x86_64體系下的參數(shù)傳遞規(guī)則
1.1 常規(guī)類型參數(shù)
這里面的常規(guī)類型參數(shù)是指除浮點(diǎn)和結(jié)構(gòu)體類型以外的參數(shù)類型,下面就是常規(guī)參數(shù)傳遞的規(guī)則:
R1: 如果函數(shù)沒(méi)有參數(shù)則除了進(jìn)行執(zhí)行函數(shù)調(diào)用外不做任何處理,如果函數(shù)有參數(shù)則在執(zhí)行函數(shù)調(diào)用指令之前需要按下面的規(guī)則設(shè)置參數(shù)值。
R2: 如果函數(shù)的參數(shù)個(gè)數(shù)<=6,則參數(shù)傳遞時(shí)將按照從左往右的定義的順序依次保存到RDI, RSI, RDX, RCX, R8, R9這6個(gè)寄存器中。
R3: 如果參數(shù)的個(gè)數(shù)>6, 那么超過(guò)6個(gè)的參數(shù),將會(huì)按從右往左的順序依次壓入到棧中。(因?yàn)闂J菑母叩刂吠偷刂愤f減的,所以從棧頂往上來(lái)算的話后面的參數(shù)依然是從左到右的順序)
R4: 如果每個(gè)參數(shù)的類型的尺寸<8個(gè)字節(jié)的情況下,則前6個(gè)參數(shù)會(huì)分別保存在上述寄存器的對(duì)應(yīng)的32位或者16位或者8位版本的寄存器中。
下面是幾個(gè)函數(shù)的定義以及在執(zhí)行這個(gè)函數(shù)調(diào)用和參數(shù)傳遞的實(shí)現(xiàn)規(guī)則(下面代碼塊中上面部分描述的函數(shù)接口,下面部分是函數(shù)調(diào)用ABI規(guī)則):
//函數(shù)的簽名 void foo1(long, long); void foo2(long, long, long, long, long, long); void foo3(long, long, long, long, long, long, long, int, short); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn) foo1(a,b) <==> RDI = a, RSI = b, call foo1 foo2(a,b,c,d,e,f) <==> RDI = a, RSI = b, RDX = c, RCX = d, R8 = e, R9 = f, call foo2 foo3(a,b,c,d,e,f,g,h,i) <== > RDI = a, RSI = b, RDX = c, RCX = d, R8 = e, R9 = f, RSP -= 2, *RSP = i, RSP-=4, *RSP = h, RSP-=8, *RSP = g, call foo3
1.2 浮點(diǎn)類型參數(shù)
如果函數(shù)參數(shù)中有浮點(diǎn)數(shù)(無(wú)論是單精度還是雙精度)類型。則參數(shù)保存的地方則不是通用寄存器,而是特定的浮點(diǎn)數(shù)寄存器。下面就是傳遞的規(guī)則:
R5: 如果浮點(diǎn)數(shù)參數(shù)的個(gè)數(shù)<=8,那么參數(shù)傳遞將按從左往右的定義順序依次保存到 XMM0 - XMM7這8個(gè)寄存器中。
R6: 如果浮點(diǎn)數(shù)參數(shù)個(gè)數(shù)>8,那么超過(guò)數(shù)量部分的參數(shù),將會(huì)按從右往左的順序依次壓入到棧中。
R7: 如果函數(shù)參數(shù)中既有浮點(diǎn)也有常規(guī)參數(shù)那么保存到寄存器中的順序和規(guī)則不會(huì)相互影響。
R8: 如果參數(shù)類型是擴(kuò)展浮點(diǎn)類型(long double),擴(kuò)展浮點(diǎn)類型的長(zhǎng)度是16個(gè)字節(jié), 那么所有的long double類型的參數(shù)都將直接壓入到棧(注意這個(gè)棧不是浮點(diǎn)寄存器棧)中而不存放到浮點(diǎn)寄存器中。
下面是幾個(gè)函數(shù)的例子:
//函數(shù)簽名
void foo4(double, double);
void foo5(double, float, double, double, double, double, double, double, float, double);
void foo6(long, double, long, double, long, long, double);
void foo7(double, long double, long);
//高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)
foo4(a,b) <==> XMM0 = a, XMM1 = b, call foo4
foo5(a,b,c,d,e,f,g,h,i,j) <==> XMM0 = a, XMM1 = b, XMM2 = c, XMM3 = d, XMM4 = e, XMM5 = f, XMM6 = g, XMM7 = h, RSP-=8, *RSP = j, RSP-=4 *RSP = i, call foo5
foo6(a,b,c,d,e,f,g) <==> RDI = a, XMM0 = b, RSI = c, XMM1 = d, RDX = e, RCX = f, XMM2 = g, call foo6
foo7(a,b,c) <==> XMM0=a, RSP-=16, *RSP = b的低8字節(jié), *(RSP+8) = b的高8字節(jié), RDI = c, call foo7
1.3 結(jié)構(gòu)體參數(shù)
針對(duì)結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體中的成員的數(shù)據(jù)類型以及結(jié)構(gòu)體的尺寸兩個(gè)因素。這里的結(jié)構(gòu)體的尺寸分為:小于等于8字節(jié)、小于等于16字節(jié)、大于16字節(jié)三種。而結(jié)構(gòu)體成員類型組成則分為:全部都是常規(guī)數(shù)據(jù)類型、全部都是浮點(diǎn)數(shù)據(jù)類型(不包括long double)、以及混合類型三種。這樣一共分為9種組合情況,下面表格描述結(jié)構(gòu)體參數(shù)的的傳遞規(guī)則:
R9:
類型/尺寸 | <=8 | <=16 | >16 |
---|---|---|---|
全部都是常規(guī)數(shù)據(jù)類型 | 6個(gè)通用寄存器中的某一個(gè) | 6個(gè)通用寄存器中的某連續(xù)兩個(gè) | 壓入棧內(nèi)存中 |
全部都是浮點(diǎn)數(shù)據(jù)類型 | 8個(gè)浮點(diǎn)寄存器中的某一個(gè) | 8個(gè)浮點(diǎn)寄存器中的某連續(xù)兩個(gè) | 壓入棧內(nèi)存中 |
混合類型 | 優(yōu)先考慮通用寄存器,再考慮浮點(diǎn)寄存器,以及成員排列的順序 | 參考左邊 | 壓入棧內(nèi)存中 |
R10: 小于等于16個(gè)字節(jié)的結(jié)構(gòu)體保存到寄存器中的規(guī)則并不是按每個(gè)數(shù)據(jù)成員來(lái)分別保存到寄存器,而是按結(jié)構(gòu)體中的內(nèi)存布局邊界順序以8字節(jié)為分割單位來(lái)保存到寄存器中的。
R11: 如果參數(shù)中混合有結(jié)構(gòu)體、常規(guī)參數(shù)、浮點(diǎn)參數(shù)則按照前10個(gè)規(guī)則分別保存?zhèn)鬟f的參數(shù)
下面就是幾個(gè)結(jié)構(gòu)體在當(dāng)做參數(shù)時(shí)的示例代碼:
//長(zhǎng)度<=8個(gè)字節(jié)的結(jié)構(gòu)體
struct S1
{
char a;
char b;
int c;
};
//長(zhǎng)度<=16的混合結(jié)構(gòu)體
struct S2
{
float a;
float b;
double c;
};
//長(zhǎng)度<=16的混合結(jié)構(gòu)體
struct S3
{
int a;
int b;
double c;
};
//長(zhǎng)度>16個(gè)字節(jié)的結(jié)構(gòu)體
struct S4
{
long a;
long b;
double c;
}
//函數(shù)簽名
void foo8(struct S1);
void foo9(struct S2);
void foo10(struct S3);
void foo11(struct S4);
//高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)
struct S1 s1;
struct S2 s2;
struct S3 s3;
struct S4 s4;
foo8(s1) <==> RDI = s1.a | (s1.b <<8) | (s1.c << 32), call foo8
foo9(s2) <==> XMM0 = s2.a | (s2.b << 32), XMM1 = s2.c, call foo9
foo10(s3) <==> RDI = s3.a | (s3.b << 32), XMM0 = s3.c, call foo10
foo11(s4) <==> RSP -= 24, *RSP = s4.a, *(RSP+8) = s4.b, *(RSP+16)=s4.c, call foo11
針對(duì)結(jié)構(gòu)體類型的參數(shù)建議是傳指針而不是傳結(jié)構(gòu)體值本身。
1.4 可變參數(shù)
可變參數(shù)函數(shù)因?yàn)槠鋮?shù)的類型和參數(shù)的數(shù)量不固定,所以系統(tǒng)在編譯時(shí)會(huì)根據(jù)函數(shù)調(diào)用時(shí)傳遞的參數(shù)的值類型而進(jìn)行不同的處理,因此規(guī)則如下:
R12: 函數(shù)調(diào)用時(shí)會(huì)根據(jù)傳遞的參數(shù)的數(shù)量和類型從左到右依次存放在對(duì)應(yīng)的6個(gè)常規(guī)參數(shù)傳遞的寄存器或者XMM0-XMM7中,如果數(shù)量超過(guò)規(guī)定則剩余的參數(shù)依次壓入棧內(nèi)存中。
R13:對(duì)于可變參數(shù)函數(shù)的調(diào)用會(huì)使用AL寄存器,其規(guī)則為:如果傳遞的可變參數(shù)中沒(méi)有浮點(diǎn)數(shù)類型則AL寄存器被設(shè)置為0,如果可變參數(shù)中出現(xiàn)了浮點(diǎn)數(shù)類型則AL寄存器會(huì)被設(shè)置為1。之所以用AL寄存器來(lái)標(biāo)志的原因是可變參數(shù)內(nèi)部實(shí)現(xiàn)因?yàn)椴恢劳獠繒?huì)傳遞什么類型的參數(shù)以及參數(shù)的個(gè)數(shù),所以內(nèi)部實(shí)現(xiàn)中會(huì)將所有作為參數(shù)傳遞的常規(guī)寄存器和作為參數(shù)傳遞的浮點(diǎn)數(shù)寄存器都會(huì)保存到一個(gè)數(shù)組中去,以方便進(jìn)行處理。因此這里借助這個(gè)AL寄存器來(lái)判斷是否有浮點(diǎn)就可以在一定程度上減少將數(shù)組的長(zhǎng)度。
下面是可變參數(shù)的調(diào)用示例:
//函數(shù)簽名 void foo12(int a, ...); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn) foo12(10,20,30.0, 40) <==> RDI = 10, RSI = 20, XMM0 = 30.0, RDX = 40,AL=1, call foo12 foo12(10,20,30,40) <==> RDI = 10, RSI = 20, RDX = 30, RCX = 40,AL=0, call foo7
一個(gè)有意思的例子: 當(dāng)調(diào)用printf函數(shù)傳遞的參數(shù)如下:
printf("%f,%d,%d", 10, 20.0, 30.0); //輸出的結(jié)果將是: 20.0,10, );
原因就是參數(shù)傳遞的規(guī)則和格式字符串不匹配導(dǎo)致的,通過(guò)上面對(duì)可變參數(shù)的傳遞規(guī)則,你能解釋為什么嗎?
2. arm32位體系下的參數(shù)傳遞規(guī)則
整個(gè)arm32位體系下的參數(shù)傳遞和參數(shù)返回都不會(huì)用到浮點(diǎn)寄存器。對(duì)于大于4字節(jié)的基本類型則會(huì)拆分為兩部分依次保存到連續(xù)的兩個(gè)寄存器中。
2.1 常規(guī)參數(shù)
R1: 對(duì)于32位的常規(guī)參數(shù),如果數(shù)量<=4則分別保存到 R0 - R3中, 如果數(shù)量>4則剩余的參數(shù)從右往左分別壓入棧內(nèi)存中。
R2: 如果參數(shù)中有64位的參數(shù)比如long long 類型,則參數(shù)會(huì)占用2個(gè)寄存器,其中低32位部分保存在前一個(gè)寄存器,高32位部分保存在后一個(gè)寄存器。
R3: 如果前面3個(gè)參數(shù)是32位的參數(shù),而第四個(gè)參數(shù)是64位的參數(shù),那么前面三個(gè)參數(shù)分別放入R0,R1,R2中,而第四個(gè)參數(shù)的低32位部分則放入R3中,高32位部分則壓入到棧內(nèi)存中。
2.2 浮點(diǎn)參數(shù)
R4: 浮點(diǎn)參數(shù)和常規(guī)參數(shù)一樣使用R0到R3寄存器,對(duì)于單精度浮點(diǎn)則使用一個(gè)寄存器,而雙精度浮點(diǎn)則使用兩個(gè)寄存器。超出部分則壓入棧內(nèi)存中。
2.3 結(jié)構(gòu)體參數(shù)
R5: arm32位系統(tǒng)的結(jié)構(gòu)體不區(qū)分成員數(shù)據(jù)類型,只區(qū)分結(jié)構(gòu)體尺寸,系統(tǒng)根據(jù)結(jié)構(gòu)體的內(nèi)存布局以4個(gè)字節(jié)為分割單位保存到寄存器或者棧內(nèi)存中。
R6: 結(jié)構(gòu)體尺寸<=4則會(huì)將參數(shù)保存到一個(gè)寄存器中,如果尺寸<=8則保存到連續(xù)的兩個(gè)寄存器中, 如果尺寸<=12則保存到3個(gè)連續(xù)的寄存器中, 如果尺寸<=16則保存到4個(gè)連續(xù)的寄存器中。如果尺寸>16則保存到棧內(nèi)存中去。
R7: 如果前3個(gè)參數(shù)都是32位的參數(shù),而第4個(gè)參數(shù)為尺寸>4的結(jié)構(gòu)體,那么第4個(gè)參數(shù)的低4個(gè)字節(jié)的部分會(huì)保存到R3中,其他部分保存到棧內(nèi)存中。
2.4 可變參數(shù)
R8: 可變參數(shù)傳遞根據(jù)參數(shù)的個(gè)數(shù)從左到右依次保存到R0-R3四個(gè)寄存器中,超過(guò)的部分從右往左依次保存到棧內(nèi)存中。 下面的實(shí)例代碼:
//函數(shù)簽名 void foo1(int a, ...); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)。 foo1(10,20,30,40,50) <==> R0 = 10, R1 = 20, R2 = 30, R3 =40, SP -=4, *SP = 50, bl foo1
3.arm64位體系下的參數(shù)傳遞規(guī)則
3.1 常規(guī)參數(shù)
這里面的常規(guī)參數(shù)是指參數(shù)的類型是非浮點(diǎn)和非結(jié)構(gòu)體類型的參數(shù),下面就是常規(guī)參數(shù)傳遞的規(guī)則:
R1: 如果函數(shù)沒(méi)有參數(shù)則除了進(jìn)行執(zhí)行函數(shù)調(diào)用外不做任何處理,如果函數(shù)有參數(shù)則在執(zhí)行函數(shù)調(diào)用指令之前需要按下面的規(guī)則設(shè)置參數(shù)值。
R2: 如果函數(shù)的參數(shù)個(gè)數(shù)<=8個(gè), 參數(shù)傳遞將按照從左往右的定義的順序依次保存到X0 - X7 這8個(gè)寄存器中。
R3: 如果參數(shù)的個(gè)數(shù)>8個(gè),那么超過(guò)數(shù)量部分的參數(shù),將會(huì)按從右往左的順序依次壓入到棧中。
R4: 如果參數(shù)的類型是小于8個(gè)字節(jié)的情況下,則前8個(gè)參數(shù)會(huì)分別保存在對(duì)應(yīng)的32位或者16位或者8位寄存器中。
下面是幾個(gè)函數(shù)的例子:
//函數(shù)簽名 void foo1(long, long); void foo2(long, long, long, long, long, long, long, long); void foo3(long, long, long, long, long, long, long, long, long, int, short); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)。 foo1(a,b) <==> X0 = a, X1 = b, bl foo1 foo2(a,b,c,d,e,f,g,h) <==>X0 = a, X1 = b, X2 = c, X3 = d, X4 = e, X5 = f, X6=g, X7 =h, bl foo2 foo3(a,b,c,d,e,f,g,h,i,j,k) <==>X0 = a, X1 = b, X2 = c, X3 = d, X4 = e, X5 = f, X6=g, X7=h, *SP -=2, *SP=k, SP-=4, *SP = j, SP-= 8, *SP = i, bl foo3
3.2 浮點(diǎn)參數(shù)
如果函數(shù)參數(shù)中有浮點(diǎn)數(shù)(無(wú)論是單精度還是雙精度)。則參數(shù)保存的地方則不是通用寄存器,而是特定的浮點(diǎn)數(shù)寄存器。系統(tǒng)提供32個(gè)128位的浮點(diǎn)寄存器Q0-Q31(V0-V31),其中的低64位則被稱為D0-D31,其中的低32位則被稱為S0-S31,其中的低16位則被稱為H0-H31,其中的低8位則被稱之為B0-B31。 也就是說(shuō)單精度浮點(diǎn)保存到S開頭的寄存器, 雙精度浮點(diǎn)保存到D開頭的寄存器。 arm系統(tǒng)中 long double 的長(zhǎng)度都是8字節(jié),因此可被當(dāng)做雙精度浮點(diǎn)。
下面就是傳遞的規(guī)則:
R5: 如果浮點(diǎn)數(shù)參數(shù)的個(gè)數(shù)<=8個(gè),那么參數(shù)傳遞將按從左往右的順序依次保存到 D0-D7或者S0-S7 這8個(gè)寄存器中。
R6: 如果浮點(diǎn)數(shù)參數(shù)個(gè)數(shù)>8個(gè)時(shí),那么超過(guò)數(shù)量部分的參數(shù),將會(huì)按從右往左的順序依次壓入到棧中。
R7: 如果函數(shù)參數(shù)中既有浮點(diǎn)也有常規(guī)參數(shù)那么保存到寄存器中的順序和規(guī)則不會(huì)相互影響。
下面是幾個(gè)函數(shù)的例子:
//函數(shù)簽名
void foo4(double, double);
void foo5(double, float, float, double, double, double, double, double, double, double);
void foo6(long, double, long, double, long, long, double);
//高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)。
foo4(double a, double b) <==> D0 = a, D1 = b, bl foo4
foo5(double a, float b, float c, double d, double e, double f, double g, double h, double i, double j) <==> D0 = a, S1 = b, S2 = c, D3 = d, D4 = e, D5 = f, D6 = g, D7 = h, *SP -=8, *SP = j, *SP -=8, *SP = i, bl foo5
foo6(long a, double b, long c, double d, long e, long f, double g) <==> X0 = a, D0 = b, X1 = c, D1 = d, X2 = e, X3 = f, D2 = g, bl foo6
3.3 結(jié)構(gòu)體參數(shù)
針對(duì)結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體的尺寸以及數(shù)據(jù)類型和數(shù)量。這里的結(jié)構(gòu)體的尺寸分別是考慮小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點(diǎn)數(shù)據(jù)成員、全部都是浮點(diǎn)數(shù)成員(這里會(huì)區(qū)分單精度和雙精度)、以及混合類型的成員(如果結(jié)構(gòu)體中有單精度和雙精度都算混合)。下面是針對(duì)結(jié)構(gòu)體參數(shù)的規(guī)則:
R8: 如果數(shù)據(jù)成員全部都是非浮點(diǎn)數(shù)據(jù)成員則 如果尺寸<=8則會(huì)將值保存到X0-X8中的某一個(gè)寄存器中, 如果尺寸<=16則會(huì)將值保存到X0-X8中的某兩個(gè)連續(xù)的寄存器中,如果尺寸>16則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進(jìn)行傳遞并保存到X0-X8中的某一個(gè)寄存器中。
R9: 如果數(shù)據(jù)成員全部都是單精度浮點(diǎn)成員則如果成員數(shù)量<=4則會(huì)將數(shù)據(jù)成員保存到S0-S7中的某4個(gè)連續(xù)的浮點(diǎn)寄存器中,如果數(shù)量>4則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進(jìn)行傳遞并保存到X0-X8中的某一個(gè)寄存器中。
R10: 如果數(shù)據(jù)成員全部都是雙精度浮點(diǎn)成員則如果成員數(shù)量<=4則會(huì)將數(shù)據(jù)成員保存到D0-D7中的某4個(gè)連續(xù)的浮點(diǎn)寄存器中,如果數(shù)量>4則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進(jìn)行傳遞并保存到X0-X8中的某一個(gè)寄存器中。
R11: 如果數(shù)據(jù)成員是混合類型的則如果尺寸<=8則保存到X0-X8中的某一個(gè)寄存器中,如果尺寸<=16則保存到X0-X8中的某兩個(gè)連續(xù)的寄存器中, 如果尺寸>16則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進(jìn)行傳遞并保存到X0-X8中的某一個(gè)寄存器中。
R12: 因?yàn)榻Y(jié)構(gòu)體參數(shù)的寄存器規(guī)則會(huì)影響到上述非結(jié)構(gòu)體參數(shù)的傳遞規(guī)則,因此一定程度上可以將結(jié)構(gòu)體當(dāng)做多個(gè)參數(shù)傳遞來(lái)看待。
下面是演示的代碼:
//長(zhǎng)度<=8個(gè)字節(jié)的結(jié)構(gòu)體
struct S1
{
char a;
char b;
int c;
};
//長(zhǎng)度<=16的單精度浮點(diǎn)結(jié)構(gòu)體
struct S2
{
float a;
float b;
float c;
};
//長(zhǎng)度<=16的混合結(jié)構(gòu)體
struct S3
{
int a;
int b;
double c;
};
//長(zhǎng)度>16個(gè)字節(jié)的結(jié)構(gòu)體
struct S4
{
long a;
long b;
double c;
}
//函數(shù)簽名
void foo8(struct S1);
void foo9(struct S2);
void foo10(struct S3);
void foo11(struct S4);
//高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn)
struct S1 s1;
struct S2 s2;
struct S3 s3;
struct S4 s4;
foo8(s1) <==> X0= s1.a | (s1.b <<8) | (s1.c << 32), bl foo8
foo9(s2) <==> S0 = s2.a, S1 = s2.b, S3 = s2.c bl foo9
foo10(s3) <==> X0 = s3.a | (s3.b << 32), X1 = s3.c, bl foo10
foo11(s4) <==> X0 = &s4, bl foo11
3.4 可變參數(shù)
可變參數(shù)函數(shù)因?yàn)槠鋮?shù)的類型和參數(shù)的數(shù)量不固定,所以系統(tǒng)在編譯時(shí)會(huì)根據(jù)函數(shù)調(diào)用時(shí)傳遞的參數(shù)的值類型而進(jìn)行不同的處理,因此規(guī)則如下:
R13: 函數(shù)調(diào)用時(shí)會(huì)根據(jù)傳遞的參數(shù)的數(shù)量和類型來(lái)決定,其中明確類型的部分按照上面介紹的規(guī)則進(jìn)行傳遞,而可變部分則從右往左依次壓入到堆棧中。
下面是示例代碼:
//函數(shù)簽名 void foo7(int a, ...); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn) foo7(10, 20, 30.0, 40) <==> X0 = 10, SP-=8, *SP = 40, SP-=8, *SP = 30.0, SP-=8, *SP = 20, bl foo7
一個(gè)有意思的例子: 當(dāng)執(zhí)行printf函數(shù)而傳遞參數(shù)如下:
printf("%f,%d,%d", 10, 20.0, 30.0); //那么輸出的結(jié)果將是: );
因?yàn)閍rm系統(tǒng)對(duì)可變參數(shù)的傳遞和x86系統(tǒng)對(duì)可變參數(shù)的處理不一致,就會(huì)出現(xiàn)真機(jī)和模擬器的結(jié)果不一致的問(wèn)題。甚至在參數(shù)傳遞規(guī)則上arm32位和arm64位系統(tǒng)都有差異。上面的參數(shù)傳遞和描述不匹配的情況下你可以說(shuō)出為什么輸出的結(jié)果不確定嗎?
四、函數(shù)返回值
函數(shù)調(diào)用除了有參數(shù)傳遞外,還有參數(shù)返回。參數(shù)的傳遞是調(diào)用者向被調(diào)函數(shù)方向的傳遞,而函數(shù)的返回則是被調(diào)用函數(shù)向調(diào)用函數(shù)方向的傳遞,因此調(diào)用者和被調(diào)用者之間應(yīng)該形成統(tǒng)一的規(guī)則。被調(diào)用函數(shù)內(nèi)對(duì)返回值的處理應(yīng)該在被調(diào)用函數(shù)返回指令執(zhí)行前。而調(diào)用函數(shù)則應(yīng)該在函數(shù)調(diào)用指令的下一條指令中盡可能早的對(duì)返回的結(jié)果進(jìn)行處理。函數(shù)的返回類型有無(wú)、非浮點(diǎn)數(shù)、浮點(diǎn)數(shù)、結(jié)構(gòu)體四種類型,因此針對(duì)不同的返回類型系統(tǒng)有不同的處理規(guī)則。
1. x86_64體系下的函數(shù)返回值規(guī)則
1.1 常規(guī)類型返回
R1: 如果函數(shù)有返回值則總是將返回值保存到RAX寄存器中。
1.2 浮點(diǎn)類型返回
R2: 返回的浮點(diǎn)數(shù)類型保存到XMM0寄存器中。
R3: 返回的(擴(kuò)展雙精度)long double 類型則保存到浮點(diǎn)寄存器棧頂中。FPU計(jì)算單元中提供了8個(gè)獨(dú)立的128位的寄存器STMM0-STMM7,這8個(gè)寄存器以堆棧形式組織在一起,統(tǒng)稱為浮點(diǎn)寄存器棧。系統(tǒng)同時(shí)也提供了專門的指令來(lái)對(duì)浮點(diǎn)寄存器棧進(jìn)行入棧和出棧處理, 編寫浮點(diǎn)指令時(shí)這些寄存器也寫作st(x),這里的x是浮點(diǎn)寄存器的索引。需要明確的是XMM系列的寄存器和STMM系列的寄存器是完全不同的兩套寄存器。
1.3 結(jié)構(gòu)體類型返回
針對(duì)結(jié)構(gòu)體類型的返回,需要考慮結(jié)構(gòu)體的尺寸以及成員的數(shù)據(jù)類型。這里的結(jié)構(gòu)體的尺寸分為:小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點(diǎn)數(shù)據(jù)成員、全部都是浮點(diǎn)數(shù)據(jù)成員(不包括 long double)、以及混合類型的成員。這樣一共分為9種情況,下面表格描述針對(duì)結(jié)構(gòu)體返回的規(guī)則:
R4
類型/尺寸 | <=8 | <=16 | >16 |
---|---|---|---|
全部非浮點(diǎn)數(shù)據(jù)成員 | RAX | RAX,RDX | 返回的結(jié)構(gòu)體將保存到RDI寄存器所指向的內(nèi)存地址中。也就是RDI寄存器是一個(gè)結(jié)構(gòu)體地址指針,這樣函數(shù)參數(shù)中的第一個(gè)參數(shù)將由保存到RDI,變?yōu)楸4娴絉SI寄存器了。 |
全部為浮點(diǎn)數(shù)據(jù)成員 | XMM0 | XMM0,XMM1 | 同上 |
混合類型 | 優(yōu)先存放到RAX,或者XMM0,然后再存放到RDX或者XMM1中。一個(gè)特殊情況就是如果成員中有l(wèi)ong double類型,則總是按>16字節(jié)的規(guī)則來(lái)處理返回值 | 同左 | 同上 |
下面是一個(gè)展示的代碼:
//長(zhǎng)度<=8個(gè)字節(jié)的結(jié)構(gòu)體 struct S1 { char a; char b; int c; }; //長(zhǎng)度<=16的混合結(jié)構(gòu)體 struct S2 { int a; int b; double c; }; //長(zhǎng)度>16個(gè)字節(jié)的結(jié)構(gòu)體 struct S3 { long a; long b; double c; } //函數(shù)簽名 struct S1 foo1(); struct S2 foo2(); struct S3 foo3(int ); //高級(jí)語(yǔ)言的函數(shù)調(diào)用以及對(duì)應(yīng)的機(jī)器指令偽代碼實(shí)現(xiàn) struct S1 s1 = foo1() <==> 函數(shù)調(diào)用時(shí):call foo1, 函數(shù)返回時(shí) s1 = RAX struct S2 s2 = foo2() <==> 函數(shù)調(diào)用時(shí):call foo2, 函數(shù)返回時(shí)s2.a&s2.b = RAX, s2.c = XMM0 struct S3 s3 = foo3(a) <==> 函數(shù)調(diào)用時(shí): RDI = &s3, RSI = a, call foo3
2. arm32位體系下的函數(shù)返回值規(guī)則
2.1 常規(guī)類型返回
R1: 函數(shù)的返回值的尺寸<=4字節(jié)則保存到R0寄存器,如果返回值的尺寸<=8字節(jié)(比如 long long類型)則保存到R0,R1寄存器其中低32位保存到R0,高32位保存到R1
2.2 浮點(diǎn)類型返回
R2: 單精度浮點(diǎn)數(shù)保存到R0寄存器,雙精度浮點(diǎn)數(shù)保存在R0,R1中其中R0保存低32位,R1保存高32位。 long double 類型的返回同雙精度浮點(diǎn)返回一致。
2.3 結(jié)構(gòu)體類型返回
R3: 不管任何類型的結(jié)構(gòu)體,總是將結(jié)構(gòu)體返回到R0寄存器所指向的內(nèi)存中, 因此R0寄存器中保存的是一個(gè)指針,這樣函數(shù)的第一個(gè)參數(shù)將保存到R1寄存器并依次往后推,也就是說(shuō)如果函數(shù)返回的是一個(gè)結(jié)構(gòu)體那么系統(tǒng)就會(huì)將返回的值當(dāng)做第一個(gè)參數(shù),而將真實(shí)的第一個(gè)參數(shù)當(dāng)做第二個(gè)參數(shù)。
下面的代碼說(shuō)明了這種情況:
struct XXX { //任意內(nèi)容 }; //函數(shù)返回結(jié)構(gòu)體 struct XXX foo(int a) { //... } 實(shí)際在編譯時(shí)會(huì)轉(zhuǎn)化為函數(shù) void foo(struct XXX *pret, int a) { }
也就是在arm32位的系統(tǒng)中凡是有結(jié)構(gòu)體作為返回的函數(shù),其實(shí)都會(huì)將結(jié)構(gòu)體指針作為函數(shù)調(diào)用的第一個(gè)參數(shù)保存到R0中,而將源代碼中的第一個(gè)參數(shù)保存到R1中。
3.arm64位體系下的函數(shù)返回值規(guī)則
2.1 常規(guī)類型返回
R1: 函數(shù)的返回參數(shù)保存到X0寄存器上
2.2 浮點(diǎn)類型返回
R2: 單精度浮點(diǎn)返回保存到S0,雙精度浮點(diǎn)返回保存到D0
2.3 結(jié)構(gòu)體類型返回
針對(duì)結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體中的成員的數(shù)據(jù)類型以及整體結(jié)構(gòu)體的尺寸。這里的結(jié)構(gòu)體的尺寸分別是考慮小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點(diǎn)數(shù)據(jù)成員、全部都是浮點(diǎn)數(shù)成員(這里會(huì)區(qū)分單精度和雙精度)、以及混合類型的成員(如果結(jié)構(gòu)體中有單精度和雙精度都算混合)。這樣一共分為9種情,下面就是針對(duì)結(jié)構(gòu)體類型返回的規(guī)則:
R3:針對(duì)非浮點(diǎn)數(shù)據(jù)成員的結(jié)構(gòu)體來(lái)說(shuō)如果結(jié)構(gòu)體的尺寸<=8,那么結(jié)構(gòu)體的值會(huì)保存到X0, 如果尺寸<=16,那么保存到X0,X1中,如果尺寸>16則結(jié)構(gòu)體返回會(huì)保存到X8寄存器所指向的內(nèi)存中,也就是X8寄存器比較特殊,專門用來(lái)保存返回的結(jié)構(gòu)體的指針。
R4: 如果結(jié)構(gòu)體的成員都是單精度并且數(shù)量<=4 則返回結(jié)構(gòu)體的每個(gè)成員分別保存到S0,S1,S2, S3四個(gè)寄存中,如果結(jié)構(gòu)體成員數(shù)量超過(guò)4個(gè)則結(jié)構(gòu)體返回會(huì)保存到X8寄存器所指向的內(nèi)存中。
R5: 如果結(jié)構(gòu)體的成員都是雙精度并且數(shù)量<=4 則返回結(jié)構(gòu)體的每個(gè)成員分別保存到D0,D1,D2,D3四個(gè)寄存器中,如果結(jié)構(gòu)體成員數(shù)量超過(guò)4個(gè)則結(jié)構(gòu)體返回會(huì)保存到X8寄存器所指向的內(nèi)存中。
R6: 如果結(jié)構(gòu)體是混合型數(shù)據(jù)成員,并且結(jié)構(gòu)體的尺寸<=8字節(jié),那么結(jié)構(gòu)體的值保存到X0, 如果尺寸<=16字節(jié)則保存到X0,X1中,如果尺寸>16則結(jié)構(gòu)體返回會(huì)保存到X8寄存器所指向的內(nèi)存中。
下面演示幾個(gè)結(jié)構(gòu)體定義以及返回結(jié)構(gòu)體的函數(shù):
//長(zhǎng)度為16字節(jié)的結(jié)構(gòu)體
struct S1
{
char a;
char b;
double c;
};
//長(zhǎng)度超過(guò)16字節(jié)的混合成員結(jié)構(gòu)體
struct S2
{
int a;
int b;
int c;
double d;
};
//長(zhǎng)度小于等于8字節(jié)的結(jié)構(gòu)體
struct S3
{
int a;
int b;
};
CGRect foo1()
{
//高級(jí)語(yǔ)言實(shí)現(xiàn)的返回
return CGRectMake(10,20,30,40);
//機(jī)器指令的函數(shù)返回的偽代碼如下:
/*
D0 = 10
D1 = 20
D2 = 30
D3 = 40
ret
*/
}
struct S1 foo2()
{
//高級(jí)語(yǔ)言實(shí)現(xiàn)的返回
return (struct S1){10, 20, 30};
//機(jī)器指令的函數(shù)返回的偽代碼如下:
/*
X0 = 10 | 20 << 8
X1 = 30
ret
*/
}
struct S2 foo3()
{
//高級(jí)語(yǔ)言實(shí)現(xiàn)的返回
return (struct S2){10, 20, 30, 40};
//機(jī)器指令的函數(shù)返回的偽代碼如下:
/*
struct S2 *p = X8 //X8中保存返回的結(jié)構(gòu)體內(nèi)存地址
p->a = 10
p->b = 20
p->c = 30
p->d = 40
ret
*/
}
struct S3 foo4()
{
//高級(jí)語(yǔ)言實(shí)現(xiàn)的返回
return (struct S3){20, 30};
//機(jī)器指令的函數(shù)返回的偽代碼如下:
/*
X0 = 20 | 30 << 32
ret
*/
}
從上面的代碼可以看出來(lái)在x86_64/arm32兩種體系結(jié)構(gòu)下如果返回的類型是結(jié)構(gòu)體并且滿足特定要求時(shí),系統(tǒng)會(huì)將結(jié)構(gòu)體指針當(dāng)做函數(shù)的第一個(gè)參數(shù),而將源代碼中的第一個(gè)參數(shù)傳遞的寄存器往后移動(dòng),而在arm64位系統(tǒng)中則x8寄存器專門負(fù)責(zé)處理返回值為特殊結(jié)構(gòu)體的情況。
六、談?wù)刼bjc_msgSend系列函數(shù)
所有的OC方法最終都會(huì)通過(guò)objc_msgSend系列函數(shù)進(jìn)行調(diào)用。這個(gè)函數(shù)系列有如下函數(shù):
objc_msgSend(void /* id self, SEL op, ... */ ) objc_msgSend_stret(void /* id self, SEL op, ... */ ) objc_msgSend_fpret(void /* id self, SEL op, ... */ ) objc_msgSend_fp2ret(void /* id self, SEL op, ... */ )
這一系列的函數(shù)的差別主要是針對(duì)返回類型的不同而使用不同的消息發(fā)送函數(shù)。
從上述的函數(shù)返回值規(guī)則可以看對(duì)于long double 類型的函數(shù)返回在x86_64位系統(tǒng)的處理方式比較特殊,其返回的值將保存在特定的浮點(diǎn)堆棧寄存器中,所以objc_msgSend_fpret函數(shù)只用在x86_64位系統(tǒng)中返回類型為long double的OC方法的消息分發(fā)中,其他體系結(jié)構(gòu)都不會(huì)用到這個(gè)函數(shù)。同樣因?yàn)镃99中引入了復(fù)數(shù)類型 _Complex 關(guān)鍵字,所以針對(duì)這種類型的 long double 返回會(huì)使用objc_msgSend_fp2ret函數(shù)。
從上述的函數(shù)的返回值規(guī)則還可以看出對(duì)于結(jié)構(gòu)體返回,如果結(jié)構(gòu)體尺寸大于一定的閾值后,x86_64位系統(tǒng)和arm32位系統(tǒng)都會(huì)將返回的結(jié)構(gòu)體轉(zhuǎn)化為第一個(gè)參數(shù)來(lái)進(jìn)行傳遞,這樣就會(huì)使得真實(shí)的參數(shù)傳遞的寄存器往后順延,而arm64則直接只用x8寄存器來(lái)保存大于閾值的結(jié)構(gòu)體指針且并不會(huì)影響到參數(shù)的傳遞順序。因此除了arm64位系統(tǒng)外其他體系結(jié)構(gòu)系統(tǒng)中針對(duì)那些返回結(jié)構(gòu)體大于一定閾值的OC方法將使用objc_msgSend_stret函數(shù)進(jìn)行消息分發(fā)。
上述的函數(shù)返回規(guī)則對(duì)
針對(duì)函數(shù)的調(diào)用、參數(shù)傳遞、函數(shù)的返回值的介紹規(guī)則就是這些了,當(dāng)然這些規(guī)則除了對(duì)普通函數(shù)適用外對(duì)OC類方法也是同樣適用的。至于一個(gè)函數(shù)內(nèi)部應(yīng)該怎樣實(shí)現(xiàn),其實(shí)也是有一定的規(guī)則的。通過(guò)這些規(guī)則你可以了解到函數(shù)是如何跟棧內(nèi)存結(jié)合在一起的,以及函數(shù)調(diào)用棧是如何被構(gòu)造出來(lái)的,你還可以了解為什么一些函數(shù)調(diào)用不會(huì)出現(xiàn)在調(diào)用棧中等等相關(guān)的知識(shí),以及可變參數(shù)函數(shù)內(nèi)部是如何實(shí)現(xiàn)的等等這部分的詳細(xì)介紹將會(huì)在: 深入iOS系統(tǒng)底層之函數(shù)(二):實(shí)現(xiàn) 進(jìn)行深入的探討。
七、參考
blog.csdn.net/q_l_s/artic…
developer.apple.com/library/arc…
armv8,armv7, x86_64位系統(tǒng)CPU手冊(cè)
blog.sina.com.cn/s/blog_8619…
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7070.html
摘要:序一直想寫一些關(guān)于系統(tǒng)底層方面的知識(shí)點(diǎn),并且醞釀了很久,后來(lái)也跟其他人交流,你為何不出一個(gè)系列呢不必要一次性把所有的東西都寫完后才發(fā)表,我聽說(shuō)后覺(jué)得非常的有道理,雖然自己的水平也很一般,但是想想自己還是有一些積累的。序 一直想寫一些關(guān)于系統(tǒng)底層方面的知識(shí)點(diǎn),并且醞釀了很久,后來(lái)也跟其他人交流,你為何不出一個(gè)系列呢? 不必要一次性把所有的東西都寫完后才發(fā)表,我聽說(shuō)后覺(jué)得非常的有道理,雖然自己的...
閱讀 740·2023-04-25 19:43
閱讀 3986·2021-11-30 14:52
閱讀 3816·2021-11-30 14:52
閱讀 3873·2021-11-29 11:00
閱讀 3808·2021-11-29 11:00
閱讀 3907·2021-11-29 11:00
閱讀 3584·2021-11-29 11:00
閱讀 6197·2021-11-29 11:00