成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入iOS系統(tǒng)底層之函數(shù)調(diào)用

番茄西紅柿 / 3586人閱讀

摘要:一般情況下程序計(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ì) 中的其他函數(shù)也是同樣適用的。


針對(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)文章

  • 深入iOS系統(tǒng)底層系列文章目錄

    摘要:序一直想寫一些關(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é)得非常的有道理,雖然自己的...

    番茄西紅柿 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<