国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

番茄西紅柿 / 3568人閱讀

摘要:一般情況下程序計數(shù)器中的值總是按照程序指令順序更新,只有在執(zhí)行跳轉(zhuǎn)指令和函數(shù)調(diào)用指令時才會打破執(zhí)行的順序。不同的體系都提供了特定的函數(shù)調(diào)用指令來實現(xiàn)函數(shù)調(diào)用的功能。位體系下的函數(shù)調(diào)用規(guī)則函數(shù)的調(diào)用函數(shù)調(diào)

古器合尺度,法物應(yīng)矩規(guī)。--蘇洵

一、什么是函數(shù)

可執(zhí)行程序是為了實現(xiàn)某個功能而由不同機器指令按特定規(guī)則進行組合排列的集合。無論高級還是低級程序語言,無論是面向?qū)ο筮€是面向過程的語言最終的代碼都會轉(zhuǎn)化為一條條機器指令的形式被執(zhí)行。為了管理上的方便和對代碼的復(fù)用,往往需要將某一段實現(xiàn)特定功能的指令集合進行抽離和處理從而形成了函數(shù)的概念,函數(shù)也可以稱之為子程序或者子例程。出現(xiàn)函數(shù)的概念后可執(zhí)行程序的機器指令集合將不再是單一的一塊代碼,而是由多個函數(shù)組成的分塊代碼,這樣可執(zhí)行程序就變成了由函數(shù)之間相互調(diào)用這種方式來構(gòu)建和組織了。

一個函數(shù)由函數(shù)簽名、參數(shù)、返回、實現(xiàn)四部分組成。函數(shù)的前三者定義了明確的邊界信息,也稱之為函數(shù)接口描述。函數(shù)接口描述的意義在于調(diào)用者不再需要了解被調(diào)用者函數(shù)的實現(xiàn)細節(jié),而只需要按被調(diào)用者的定義的接口進行交互即可。如何去定義一個函數(shù),如何去實現(xiàn)一個函數(shù),如何去調(diào)用一個函數(shù),如何將參數(shù)傳遞給被調(diào)用的函數(shù),如何使用被調(diào)用者函數(shù)的返回這些都需要有統(tǒng)一的標準規(guī)范來進行界定,這個規(guī)則有兩個層面的標準:在高級語言層面的規(guī)則稱之為API規(guī)則;而在機器指令層面上則由于不同的操作系統(tǒng)以及不同的CPU體系結(jié)構(gòu)下提供的指令集和構(gòu)造程序的方式不同而不同,所以在系統(tǒng)層面的規(guī)則稱之為ABI規(guī)則。本文的重點是詳細介紹函數(shù)調(diào)用、函數(shù)參數(shù)傳遞、函數(shù)返回值這3個方面的ABI規(guī)則,通過對這些規(guī)則的詳細介紹相信您對什么是函數(shù)就會有更加深入的了解。需要注意的是這里的ABI規(guī)則是指基于OC語言實現(xiàn)的程序的ABI規(guī)則,這些規(guī)則并不適用于通過Swift實現(xiàn)的程序以及不適用于Linux等其他操作系統(tǒng)的ABI規(guī)則。

由于內(nèi)容過多因此我將分為兩篇文章來做具體介紹,前一篇文章介紹函數(shù)接口相關(guān)的內(nèi)容,后一篇文章介紹函數(shù)實現(xiàn)相關(guān)的內(nèi)容。

二、函數(shù)調(diào)用

CPU中的程序計數(shù)器(IP/PC)中總是保存著下一條將要執(zhí)行的指令的內(nèi)存地址,這樣每執(zhí)行一條指令就會更新程序計數(shù)器中的值,從而可以繼續(xù)執(zhí)行下一條指令。系統(tǒng)就是這樣通過不停的變化程序計數(shù)器中的值來實現(xiàn)程序指令的執(zhí)行的。一般情況下程序計數(shù)器中的值總是按照程序指令順序更新,只有在執(zhí)行跳轉(zhuǎn)指令和函數(shù)調(diào)用指令時才會打破執(zhí)行的順序。

函數(shù)調(diào)用的本質(zhì)就是將函數(shù)在內(nèi)存中的首地址賦值給程序計數(shù)器(IP/PC),這樣下一條執(zhí)行的指令就變?yōu)榱撕瘮?shù)首地址處的指令,從而實現(xiàn)函數(shù)的調(diào)用。除了要更新程序計數(shù)器的值外還需要保存調(diào)用現(xiàn)場,以便當函數(shù)調(diào)用返回后繼續(xù)執(zhí)行函數(shù)調(diào)用的下一條指令,所以這里所謂的保存調(diào)用現(xiàn)場就是將函數(shù)調(diào)用的下一條指令的地址保存起來。不同的CPU體系都提供了特定的函數(shù)調(diào)用指令來實現(xiàn)函數(shù)調(diào)用的功能。比如x86系統(tǒng)提供一條稱之為call的指令來實現(xiàn)函數(shù)調(diào)用,call指令除了會更新程序計數(shù)器的值外還會把函數(shù)調(diào)用的下一條指令壓入到棧中進行保存;arm系統(tǒng)則提供b系列的指令來實現(xiàn)函數(shù)調(diào)用,b系列指令除了會更新程序計數(shù)器的值外還會把函數(shù)調(diào)用的下一條指令保存到LR寄存器中。

函數(shù)返回的本質(zhì)就是將前面說到的保存的調(diào)用現(xiàn)場地址賦值給程序計數(shù)器,這樣下一條執(zhí)行的指令就變?yōu)榱苏{(diào)用者調(diào)用被調(diào)函數(shù)的下一條指令了。不同的CPU體系也都提供了特定的函數(shù)返回指令來實現(xiàn)函數(shù)返回的功能(arm32位系統(tǒng)除外)。比如x86系統(tǒng)提供一條稱之為ret的指令來實現(xiàn)函數(shù)返回,此指令會將棧頂保存的地址賦值給程序計數(shù)器然后執(zhí)行出棧操作;arm64位系統(tǒng)也提供一條ret指令來實現(xiàn)函數(shù)的返回,此指令則會把當前的LR寄存器的值賦值給程序計數(shù)器。

對于x86系統(tǒng)來說因為執(zhí)行函數(shù)調(diào)用前會將調(diào)用者的下一條指令壓入棧中,而被調(diào)用者函數(shù)內(nèi)部因為有本地棧幀(stack frame)的定義又會將棧頂下移,所以在被調(diào)用者函數(shù)執(zhí)行ret指令返回之前需要確保當前堆棧寄存器SP所指向的棧頂?shù)刂芬捅徽{(diào)用函數(shù)執(zhí)行前的棧頂?shù)刂繁3忠恢拢蝗划攔et指令執(zhí)行時取出的調(diào)用者的下一條指令的值將是錯誤的,從而會產(chǎn)生崩潰異常。

對于arm系統(tǒng)來說因為LR寄存器只有一個,因此如果被調(diào)用函數(shù)內(nèi)部也調(diào)用其他函數(shù)時也會更新LR寄存器的值,一旦LR寄存器被更新后將無法恢復(fù)正確的調(diào)用現(xiàn)場,所以一般情況下被調(diào)用函數(shù)的前幾條指令做的事情就是將LR寄存器的值保存到棧內(nèi)存中,而被調(diào)用函數(shù)的最后幾條指令所的事情就是將棧內(nèi)存中保存的內(nèi)容恢復(fù)到LR寄存器。

有一種特殊的函數(shù)調(diào)用場景就是當函數(shù)調(diào)用發(fā)生在調(diào)用者函數(shù)的最后一條指令時,則不需要進行調(diào)用現(xiàn)場的保護處理,同時也會將函數(shù)調(diào)用指令改為跳轉(zhuǎn)指令,原因是因為調(diào)用者的最后一條指令再無下一條有效的指令,而仍然采用調(diào)用指令的話則保存的調(diào)用現(xiàn)場則是個無效的地址,這樣當函數(shù)返回時將跳轉(zhuǎn)到這個無效的地址從而產(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 指令。在匯編語言中call 指令后面的操作數(shù)是調(diào)用的目標函數(shù)的絕對地址,而實際的機器指令中的操作數(shù)則是一個相對地址值,這個地址值是目標函數(shù)地址距離當前指令地址的相對偏移值。無論是x86系統(tǒng)還是arm系統(tǒng)如果指令中的操作數(shù)部分的值是內(nèi)存地址的話,一般都是相對當前指令的偏移地址而不是絕對地址。下面就是函數(shù)調(diào)用指令以及其內(nèi)部實現(xiàn)的等價操作。

call YY1   <==>   RIP = YY1,   RSP = RSP-8,  *RSP = XX3

也就是說執(zhí)行一條函數(shù)調(diào)用指令等價于將指令中的地址賦值給IP寄存器,同時把函數(shù)的返回地址壓入棧寄存器中去。

1.2 函數(shù)的跳轉(zhuǎn)

函數(shù)跳轉(zhuǎn)的指令是jmp指令。在匯編語言中jmp 指令后面的操作數(shù)是調(diào)用的目標函數(shù)的絕對地址,而實際的機器指令中的操作數(shù)則是一個相對地址值,這個地址值是目標函數(shù)地址距離當前指令地址的相對偏移值,下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實現(xiàn)的等價操作。

  jmp ZZ1  <==>  RIP = ZZ1

也就是說執(zhí)行一條跳轉(zhuǎn)指令等價于將指令中的地址賦值給IP寄存器。

1.3 函數(shù)的返回

函數(shù)返回的指令是ret指令。ret指令后面一般不跟操作數(shù),下面就是函數(shù)返回指令以及其內(nèi)部實現(xiàn)的等價操作。

 ret   <==>   RIP = *RSP,   RSP = RSP + 8

也就是說執(zhí)行一條ret指令等價于將當前棧寄存器中的值賦值給IP寄存器,同時棧寄存器執(zhí)行POP操作。

2. arm32位體系下的函數(shù)調(diào)用規(guī)則

2.1 函數(shù)的調(diào)用

函數(shù)的調(diào)用指令為bl/blx。 這兩條指令的操作數(shù)可以是相對地址偏移也可以是寄存器。bl/blx的區(qū)別就是bl函數(shù)調(diào)用不會切換指令集,而blx調(diào)用則會從thumb指令集切換到arm指令集或者相反切換。arm32系統(tǒng)中存在著兩套指令集即thumb指令集和arm指令集,其中的arm指令集中的所有的指令的長度都是32位而thumb指令集則存在著32位和16位兩種長度的指令集。兩種指令集是以函數(shù)為單位進行使用的,也就是說一個函數(shù)中的所有指令要么都是arm指令要么就都是thumb指令。正是因為如此如果調(diào)用者函數(shù)和被調(diào)用者函數(shù)之間用的是不同的指令集則需要通過blx來執(zhí)行函數(shù)調(diào)用,而如果二者所用的指令集相同則需要通過bl指令來執(zhí)行調(diào)用。下面就是函數(shù)調(diào)用指令以及其內(nèi)部實現(xiàn)的等價操作。

 bl/blx  YY1  <==>  PC = YY1,  LR = XX3

也就是說執(zhí)行一條函數(shù)調(diào)用指令等價于將指令中的地址賦值給PC寄存器,同時把函數(shù)的返回地址賦值給LR寄存器中去。

2.2 函數(shù)的跳轉(zhuǎn)

函數(shù)的跳轉(zhuǎn)指令是b/bx, 這兩條指令的操作數(shù)可以是相對地址偏移也可以是寄存器,b/bx的區(qū)別就是b函數(shù)調(diào)用不會切換指令集。下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實現(xiàn)的等價操作。

b/bx ZZ1   <==>  PC = ZZ1

也就是說跳轉(zhuǎn)指令等價于將指令中的地址賦值給PC寄存器。

2.3 函數(shù)的返回

arm32位系統(tǒng)沒有專門的函數(shù)返回ret指令,因為arm32位系統(tǒng)可以直接修改PC寄存器的值,所以函數(shù)返回可以直接給PC指令賦值,也可以通過調(diào)用b/bx LR 來實現(xiàn)函數(shù)的返回處理。

b/bx LR
//或者
mov PC, XXX

arm32位系統(tǒng)可以直接修改PC寄存器的值,因此函數(shù)返回時可以直接設(shè)置PC寄存器的值為函數(shù)的返回地址,也可以執(zhí)行b/bx跳轉(zhuǎn)指令并指定目標地址為LR寄存器中的值。

3.arm64位體系下的函數(shù)調(diào)用規(guī)則

3.1 函數(shù)的調(diào)用

函數(shù)調(diào)用的指令是bl/blr 其中bl指令的操作數(shù)是距離當前位置相對距離的偏移地址,blr指令的操作數(shù)則是寄存器,表明調(diào)用寄存器所指定的地址。因為bl指令中的操作數(shù)部分是函數(shù)的相對偏移地址,又因為arm64位系統(tǒng)的一條指令占用4個字節(jié),根據(jù)指令的定義bl指令所能跳轉(zhuǎn)的范圍是距離當前位置±32MB的范圍,所以如果要跳轉(zhuǎn)到更遠的地址則需要借助blr指令。 下面就是函數(shù)調(diào)用指令以及其內(nèi)部實現(xiàn)的等價操作。

//如果YY1地址離調(diào)用指令的距離是在±32MB內(nèi)則使用bl指令即可。
 bl YY1 <==>  PC = YY1, LR = XX3

//如果YY1地址離調(diào)用指令的距離超過±32MB則使用blr指令執(zhí)行間接調(diào)用。
ldr  x16,  YY1
blr  x16

也就是說執(zhí)行一條函數(shù)調(diào)用指令等價于將指令中的地址賦值給PC寄存器,同時把函數(shù)的返回地址賦值給LR寄存器中去。

3.2函數(shù)的跳轉(zhuǎn)

函數(shù)跳轉(zhuǎn)的指令是b/br, 其中b指令的操作數(shù)是距離當前位置相對距離的偏移地址,br指令的操作數(shù)則是寄存器,表明跳轉(zhuǎn)到寄存器所指定的地址中去。下面就是函數(shù)跳轉(zhuǎn)指令以及其內(nèi)部實現(xiàn)的等價操作。

b ZZ1   <==>  PC = ZZ1

也就是說跳轉(zhuǎn)指令等價于將指令中的地址賦值給PC寄存器。

3.3 函數(shù)的返回

函數(shù)返回的指令是 ret, 下面就是函數(shù)返回指令以及其內(nèi)部實現(xiàn)的等價操作。

 ret  <==>   PC = LR

也就是說執(zhí)行一條ret指令等價于將LR寄存器中的值賦值給PC寄存器。

三、函數(shù)參數(shù)傳遞

某些函數(shù)定義中有參數(shù)需要傳遞,需要由調(diào)用者函數(shù)將參數(shù)傳遞給被調(diào)用者函數(shù),因此在調(diào)用這類函數(shù)時,需要在執(zhí)行函數(shù)調(diào)用指令之前,進行函數(shù)參數(shù)的傳遞。函數(shù)的參數(shù)個數(shù)可以為0個,也可以為某個固定的數(shù)量,也可以為任意數(shù)量(可變參數(shù))。 函數(shù)的每個參數(shù)類型可以是整型數(shù)據(jù)類型,也可以是浮點數(shù)據(jù)類型,也可以是指針,也可以是結(jié)構(gòu)體。因此在函數(shù)傳遞的規(guī)則上需要明確指出調(diào)用者應(yīng)該如何將參數(shù)進行保存處理,而被調(diào)用者又是從什么地方來獲取這些外部傳遞進來的參數(shù)值。不同體系下的系統(tǒng)會根據(jù)參數(shù)定義的個數(shù)和類型來制定不同的規(guī)則。一般情況下各系統(tǒng)都會約定一些特定的寄存器來進行參數(shù)傳遞交換,或者使用棧內(nèi)存來進行參數(shù)傳遞交換。

1. x86_64體系下的參數(shù)傳遞規(guī)則

1.1 常規(guī)類型參數(shù)

這里面的常規(guī)類型參數(shù)是指除浮點和結(jié)構(gòu)體類型以外的參數(shù)類型,下面就是常規(guī)參數(shù)傳遞的規(guī)則:

R1: 如果函數(shù)沒有參數(shù)則除了進行執(zhí)行函數(shù)調(diào)用外不做任何處理,如果函數(shù)有參數(shù)則在執(zhí)行函數(shù)調(diào)用指令之前需要按下面的規(guī)則設(shè)置參數(shù)值。

R2: 如果函數(shù)的參數(shù)個數(shù)<=6,則參數(shù)傳遞時將按照從左往右的定義的順序依次保存到RDI, RSI, RDX, RCX, R8, R9這6個寄存器中。

R3: 如果參數(shù)的個數(shù)>6, 那么超過6個的參數(shù),將會按從右往左的順序依次壓入到棧中。(因為棧是從高地址往低地址遞減的,所以從棧頂往上來算的話后面的參數(shù)依然是從左到右的順序)

R4: 如果每個參數(shù)的類型的尺寸<8個字節(jié)的情況下,則前6個參數(shù)會分別保存在上述寄存器的對應(yīng)的32位或者16位或者8位版本的寄存器中。

下面是幾個函數(shù)的定義以及在執(zhí)行這個函數(shù)調(diào)用和參數(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);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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 浮點類型參數(shù)

如果函數(shù)參數(shù)中有浮點數(shù)(無論是單精度還是雙精度)類型。則參數(shù)保存的地方則不是通用寄存器,而是特定的浮點數(shù)寄存器。下面就是傳遞的規(guī)則:

R5: 如果浮點數(shù)參數(shù)的個數(shù)<=8,那么參數(shù)傳遞將按從左往右的定義順序依次保存到 XMM0 - XMM7這8個寄存器中。

R6: 如果浮點數(shù)參數(shù)個數(shù)>8,那么超過數(shù)量部分的參數(shù),將會按從右往左的順序依次壓入到棧中。

R7: 如果函數(shù)參數(shù)中既有浮點也有常規(guī)參數(shù)那么保存到寄存器中的順序和規(guī)則不會相互影響。

R8: 如果參數(shù)類型是擴展浮點類型(long double),擴展浮點類型的長度是16個字節(jié), 那么所有的long double類型的參數(shù)都將直接壓入到棧(注意這個棧不是浮點寄存器棧)中而不存放到浮點寄存器中。

下面是幾個函數(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);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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ù)

針對結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體中的成員的數(shù)據(jù)類型以及結(jié)構(gòu)體的尺寸兩個因素。這里的結(jié)構(gòu)體的尺寸分為:小于等于8字節(jié)、小于等于16字節(jié)、大于16字節(jié)三種。而結(jié)構(gòu)體成員類型組成則分為:全部都是常規(guī)數(shù)據(jù)類型、全部都是浮點數(shù)據(jù)類型(不包括long double)、以及混合類型三種。這樣一共分為9種組合情況,下面表格描述結(jié)構(gòu)體參數(shù)的的傳遞規(guī)則:

R9:

類型/尺寸 <=8 <=16 >16
全部都是常規(guī)數(shù)據(jù)類型 6個通用寄存器中的某一個 6個通用寄存器中的某連續(xù)兩個 壓入棧內(nèi)存中
全部都是浮點數(shù)據(jù)類型 8個浮點寄存器中的某一個 8個浮點寄存器中的某連續(xù)兩個 壓入棧內(nèi)存中
混合類型 優(yōu)先考慮通用寄存器,再考慮浮點寄存器,以及成員排列的順序 參考左邊 壓入棧內(nèi)存中

R10: 小于等于16個字節(jié)的結(jié)構(gòu)體保存到寄存器中的規(guī)則并不是按每個數(shù)據(jù)成員來分別保存到寄存器,而是按結(jié)構(gòu)體中的內(nèi)存布局邊界順序以8字節(jié)為分割單位來保存到寄存器中的。

R11: 如果參數(shù)中混合有結(jié)構(gòu)體、常規(guī)參數(shù)、浮點參數(shù)則按照前10個規(guī)則分別保存?zhèn)鬟f的參數(shù)

下面就是幾個結(jié)構(gòu)體在當做參數(shù)時的示例代碼:

//長度<=8個字節(jié)的結(jié)構(gòu)體
struct S1
{
    char a;
    char b;
    int c;
};

//長度<=16的混合結(jié)構(gòu)體
struct S2
{
   float a;
   float b;
   double c;
};

//長度<=16的混合結(jié)構(gòu)體
struct S3
{
  int a;
  int b;
  double c;
};

//長度>16個字節(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);


//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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

針對結(jié)構(gòu)體類型的參數(shù)建議是傳指針而不是傳結(jié)構(gòu)體值本身。

1.4 可變參數(shù)

可變參數(shù)函數(shù)因為其參數(shù)的類型和參數(shù)的數(shù)量不固定,所以系統(tǒng)在編譯時會根據(jù)函數(shù)調(diào)用時傳遞的參數(shù)的值類型而進行不同的處理,因此規(guī)則如下:

R12: 函數(shù)調(diào)用時會根據(jù)傳遞的參數(shù)的數(shù)量和類型從左到右依次存放在對應(yīng)的6個常規(guī)參數(shù)傳遞的寄存器或者XMM0-XMM7中,如果數(shù)量超過規(guī)定則剩余的參數(shù)依次壓入棧內(nèi)存中。

R13:對于可變參數(shù)函數(shù)的調(diào)用會使用AL寄存器,其規(guī)則為:如果傳遞的可變參數(shù)中沒有浮點數(shù)類型則AL寄存器被設(shè)置為0,如果可變參數(shù)中出現(xiàn)了浮點數(shù)類型則AL寄存器會被設(shè)置為1。之所以用AL寄存器來標志的原因是可變參數(shù)內(nèi)部實現(xiàn)因為不知道外部會傳遞什么類型的參數(shù)以及參數(shù)的個數(shù),所以內(nèi)部實現(xiàn)中會將所有作為參數(shù)傳遞的常規(guī)寄存器和作為參數(shù)傳遞的浮點數(shù)寄存器都會保存到一個數(shù)組中去,以方便進行處理。因此這里借助這個AL寄存器來判斷是否有浮點就可以在一定程度上減少將數(shù)組的長度。

下面是可變參數(shù)的調(diào)用示例:

//函數(shù)簽名
void foo12(int a, ...);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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

一個有意思的例子: 當調(diào)用printf函數(shù)傳遞的參數(shù)如下:

printf("%f,%d,%d", 10, 20.0, 30.0);       //輸出的結(jié)果將是: 20.0,10, );

原因就是參數(shù)傳遞的規(guī)則和格式字符串不匹配導致的,通過上面對可變參數(shù)的傳遞規(guī)則,你能解釋為什么嗎?

2. arm32位體系下的參數(shù)傳遞規(guī)則

整個arm32位體系下的參數(shù)傳遞和參數(shù)返回都不會用到浮點寄存器。對于大于4字節(jié)的基本類型則會拆分為兩部分依次保存到連續(xù)的兩個寄存器中。

2.1 常規(guī)參數(shù)

R1: 對于32位的常規(guī)參數(shù),如果數(shù)量<=4則分別保存到 R0 - R3中, 如果數(shù)量>4則剩余的參數(shù)從右往左分別壓入棧內(nèi)存中。

R2: 如果參數(shù)中有64位的參數(shù)比如long long 類型,則參數(shù)會占用2個寄存器,其中低32位部分保存在前一個寄存器,高32位部分保存在后一個寄存器。

R3: 如果前面3個參數(shù)是32位的參數(shù),而第四個參數(shù)是64位的參數(shù),那么前面三個參數(shù)分別放入R0,R1,R2中,而第四個參數(shù)的低32位部分則放入R3中,高32位部分則壓入到棧內(nèi)存中。

2.2 浮點參數(shù)

R4: 浮點參數(shù)和常規(guī)參數(shù)一樣使用R0到R3寄存器,對于單精度浮點則使用一個寄存器,而雙精度浮點則使用兩個寄存器。超出部分則壓入棧內(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個字節(jié)為分割單位保存到寄存器或者棧內(nèi)存中。

R6: 結(jié)構(gòu)體尺寸<=4則會將參數(shù)保存到一個寄存器中,如果尺寸<=8則保存到連續(xù)的兩個寄存器中, 如果尺寸<=12則保存到3個連續(xù)的寄存器中, 如果尺寸<=16則保存到4個連續(xù)的寄存器中。如果尺寸>16則保存到棧內(nèi)存中去。

R7: 如果前3個參數(shù)都是32位的參數(shù),而第4個參數(shù)為尺寸>4的結(jié)構(gòu)體,那么第4個參數(shù)的低4個字節(jié)的部分會保存到R3中,其他部分保存到棧內(nèi)存中。

2.4 可變參數(shù)

R8: 可變參數(shù)傳遞根據(jù)參數(shù)的個數(shù)從左到右依次保存到R0-R3四個寄存器中,超過的部分從右往左依次保存到棧內(nèi)存中。 下面的實例代碼:

//函數(shù)簽名
void foo1(int a, ...);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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ù)的類型是非浮點和非結(jié)構(gòu)體類型的參數(shù),下面就是常規(guī)參數(shù)傳遞的規(guī)則:

R1: 如果函數(shù)沒有參數(shù)則除了進行執(zhí)行函數(shù)調(diào)用外不做任何處理,如果函數(shù)有參數(shù)則在執(zhí)行函數(shù)調(diào)用指令之前需要按下面的規(guī)則設(shè)置參數(shù)值。

R2: 如果函數(shù)的參數(shù)個數(shù)<=8個, 參數(shù)傳遞將按照從左往右的定義的順序依次保存到X0 - X7 這8個寄存器中。

R3: 如果參數(shù)的個數(shù)>8個,那么超過數(shù)量部分的參數(shù),將會按從右往左的順序依次壓入到棧中。

R4: 如果參數(shù)的類型是小于8個字節(jié)的情況下,則前8個參數(shù)會分別保存在對應(yīng)的32位或者16位或者8位寄存器中。

下面是幾個函數(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);


//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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 浮點參數(shù)

如果函數(shù)參數(shù)中有浮點數(shù)(無論是單精度還是雙精度)。則參數(shù)保存的地方則不是通用寄存器,而是特定的浮點數(shù)寄存器。系統(tǒng)提供32個128位的浮點寄存器Q0-Q31(V0-V31),其中的低64位則被稱為D0-D31,其中的低32位則被稱為S0-S31,其中的低16位則被稱為H0-H31,其中的低8位則被稱之為B0-B31。 也就是說單精度浮點保存到S開頭的寄存器, 雙精度浮點保存到D開頭的寄存器。 arm系統(tǒng)中 long double 的長度都是8字節(jié),因此可被當做雙精度浮點。

下面就是傳遞的規(guī)則:

R5: 如果浮點數(shù)參數(shù)的個數(shù)<=8個,那么參數(shù)傳遞將按從左往右的順序依次保存到 D0-D7或者S0-S7 這8個寄存器中。

R6: 如果浮點數(shù)參數(shù)個數(shù)>8個時,那么超過數(shù)量部分的參數(shù),將會按從右往左的順序依次壓入到棧中。

R7: 如果函數(shù)參數(shù)中既有浮點也有常規(guī)參數(shù)那么保存到寄存器中的順序和規(guī)則不會相互影響。

下面是幾個函數(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);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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ù)

針對結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體的尺寸以及數(shù)據(jù)類型和數(shù)量。這里的結(jié)構(gòu)體的尺寸分別是考慮小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點數(shù)據(jù)成員、全部都是浮點數(shù)成員(這里會區(qū)分單精度和雙精度)、以及混合類型的成員(如果結(jié)構(gòu)體中有單精度和雙精度都算混合)。下面是針對結(jié)構(gòu)體參數(shù)的規(guī)則:

R8: 如果數(shù)據(jù)成員全部都是非浮點數(shù)據(jù)成員則 如果尺寸<=8則會將值保存到X0-X8中的某一個寄存器中, 如果尺寸<=16則會將值保存到X0-X8中的某兩個連續(xù)的寄存器中,如果尺寸>16則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進行傳遞并保存到X0-X8中的某一個寄存器中。

R9: 如果數(shù)據(jù)成員全部都是單精度浮點成員則如果成員數(shù)量<=4則會將數(shù)據(jù)成員保存到S0-S7中的某4個連續(xù)的浮點寄存器中,如果數(shù)量>4則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進行傳遞并保存到X0-X8中的某一個寄存器中。

R10: 如果數(shù)據(jù)成員全部都是雙精度浮點成員則如果成員數(shù)量<=4則會將數(shù)據(jù)成員保存到D0-D7中的某4個連續(xù)的浮點寄存器中,如果數(shù)量>4則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進行傳遞并保存到X0-X8中的某一個寄存器中。

R11: 如果數(shù)據(jù)成員是混合類型的則如果尺寸<=8則保存到X0-X8中的某一個寄存器中,如果尺寸<=16則保存到X0-X8中的某兩個連續(xù)的寄存器中, 如果尺寸>16則結(jié)構(gòu)體將不再按值傳遞而是以指針的形式進行傳遞并保存到X0-X8中的某一個寄存器中。

R12: 因為結(jié)構(gòu)體參數(shù)的寄存器規(guī)則會影響到上述非結(jié)構(gòu)體參數(shù)的傳遞規(guī)則,因此一定程度上可以將結(jié)構(gòu)體當做多個參數(shù)傳遞來看待。

下面是演示的代碼:

//長度<=8個字節(jié)的結(jié)構(gòu)體
struct S1
{
    char a;
    char b;
    int c;
};

//長度<=16的單精度浮點結(jié)構(gòu)體
struct S2
{
   float a;
   float b;
   float c;
};

//長度<=16的混合結(jié)構(gòu)體
struct S3
{
  int a;
  int b;
  double c;
};

//長度>16個字節(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);


//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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ù)因為其參數(shù)的類型和參數(shù)的數(shù)量不固定,所以系統(tǒng)在編譯時會根據(jù)函數(shù)調(diào)用時傳遞的參數(shù)的值類型而進行不同的處理,因此規(guī)則如下:

R13: 函數(shù)調(diào)用時會根據(jù)傳遞的參數(shù)的數(shù)量和類型來決定,其中明確類型的部分按照上面介紹的規(guī)則進行傳遞,而可變部分則從右往左依次壓入到堆棧中。

下面是示例代碼:

//函數(shù)簽名
void foo7(int a, ...);

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(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

一個有意思的例子: 當執(zhí)行printf函數(shù)而傳遞參數(shù)如下:

 printf("%f,%d,%d", 10, 20.0, 30.0);     //那么輸出的結(jié)果將是: );

因為arm系統(tǒng)對可變參數(shù)的傳遞和x86系統(tǒng)對可變參數(shù)的處理不一致,就會出現(xiàn)真機和模擬器的結(jié)果不一致的問題。甚至在參數(shù)傳遞規(guī)則上arm32位和arm64位系統(tǒng)都有差異。上面的參數(shù)傳遞和描述不匹配的情況下你可以說出為什么輸出的結(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)對返回值的處理應(yīng)該在被調(diào)用函數(shù)返回指令執(zhí)行前。而調(diào)用函數(shù)則應(yīng)該在函數(shù)調(diào)用指令的下一條指令中盡可能早的對返回的結(jié)果進行處理。函數(shù)的返回類型有無、非浮點數(shù)、浮點數(shù)、結(jié)構(gòu)體四種類型,因此針對不同的返回類型系統(tǒng)有不同的處理規(guī)則。

1. x86_64體系下的函數(shù)返回值規(guī)則

1.1 常規(guī)類型返回

R1: 如果函數(shù)有返回值則總是將返回值保存到RAX寄存器中。

1.2 浮點類型返回

R2: 返回的浮點數(shù)類型保存到XMM0寄存器中。

R3: 返回的(擴展雙精度)long double 類型則保存到浮點寄存器棧頂中。FPU計算單元中提供了8個獨立的128位的寄存器STMM0-STMM7,這8個寄存器以堆棧形式組織在一起,統(tǒng)稱為浮點寄存器棧。系統(tǒng)同時也提供了專門的指令來對浮點寄存器棧進行入棧和出棧處理, 編寫浮點指令時這些寄存器也寫作st(x),這里的x是浮點寄存器的索引。需要明確的是XMM系列的寄存器和STMM系列的寄存器是完全不同的兩套寄存器。

1.3 結(jié)構(gòu)體類型返回

針對結(jié)構(gòu)體類型的返回,需要考慮結(jié)構(gòu)體的尺寸以及成員的數(shù)據(jù)類型。這里的結(jié)構(gòu)體的尺寸分為:小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點數(shù)據(jù)成員、全部都是浮點數(shù)據(jù)成員(不包括 long double)、以及混合類型的成員。這樣一共分為9種情況,下面表格描述針對結(jié)構(gòu)體返回的規(guī)則:

R4

類型/尺寸 <=8 <=16 >16
全部非浮點數(shù)據(jù)成員 RAX RAX,RDX 返回的結(jié)構(gòu)體將保存到RDI寄存器所指向的內(nèi)存地址中。也就是RDI寄存器是一個結(jié)構(gòu)體地址指針,這樣函數(shù)參數(shù)中的第一個參數(shù)將由保存到RDI,變?yōu)楸4娴絉SI寄存器了。
全部為浮點數(shù)據(jù)成員 XMM0 XMM0,XMM1 同上
混合類型 優(yōu)先存放到RAX,或者XMM0,然后再存放到RDX或者XMM1中。一個特殊情況就是如果成員中有l(wèi)ong double類型,則總是按>16字節(jié)的規(guī)則來處理返回值 同左 同上

下面是一個展示的代碼:

//長度<=8個字節(jié)的結(jié)構(gòu)體
struct S1
{
    char a;
    char b;
    int c;
};

//長度<=16的混合結(jié)構(gòu)體
struct S2
{
  int a;
  int b;
  double c;
};

//長度>16個字節(jié)的結(jié)構(gòu)體
struct S3
{
   long a;
   long b;
   double c;
}

 //函數(shù)簽名
 struct S1 foo1();
 struct S2 foo2();
 struct S3 foo3(int );

//高級語言的函數(shù)調(diào)用以及對應(yīng)的機器指令偽代碼實現(xiàn)
struct S1 s1 = foo1()  <==>  函數(shù)調(diào)用時:call foo1,  函數(shù)返回時 s1 = RAX
struct S2 s2 = foo2() <==> 函數(shù)調(diào)用時:call foo2, 函數(shù)返回時s2.a&s2.b = RAX, s2.c = XMM0
struct S3 s3 = foo3(a)  <==> 函數(shù)調(diào)用時: 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 浮點類型返回

R2: 單精度浮點數(shù)保存到R0寄存器,雙精度浮點數(shù)保存在R0,R1中其中R0保存低32位,R1保存高32位。 long double 類型的返回同雙精度浮點返回一致。

2.3 結(jié)構(gòu)體類型返回

R3: 不管任何類型的結(jié)構(gòu)體,總是將結(jié)構(gòu)體返回到R0寄存器所指向的內(nèi)存中, 因此R0寄存器中保存的是一個指針,這樣函數(shù)的第一個參數(shù)將保存到R1寄存器并依次往后推,也就是說如果函數(shù)返回的是一個結(jié)構(gòu)體那么系統(tǒng)就會將返回的值當做第一個參數(shù),而將真實的第一個參數(shù)當做第二個參數(shù)。

下面的代碼說明了這種情況:

struct XXX
{
  //任意內(nèi)容
};

//函數(shù)返回結(jié)構(gòu)體
struct XXX foo(int a)
{
   //...
}

實際在編譯時會轉(zhuǎn)化為函數(shù)
void foo(struct XXX *pret, int a)
{
}

也就是在arm32位的系統(tǒng)中凡是有結(jié)構(gòu)體作為返回的函數(shù),其實都會將結(jié)構(gòu)體指針作為函數(shù)調(diào)用的第一個參數(shù)保存到R0中,而將源代碼中的第一個參數(shù)保存到R1中。

3.arm64位體系下的函數(shù)返回值規(guī)則

2.1 常規(guī)類型返回

R1: 函數(shù)的返回參數(shù)保存到X0寄存器上

2.2 浮點類型返回

R2: 單精度浮點返回保存到S0,雙精度浮點返回保存到D0

2.3 結(jié)構(gòu)體類型返回

針對結(jié)構(gòu)體類型的參數(shù),需要考慮結(jié)構(gòu)體中的成員的數(shù)據(jù)類型以及整體結(jié)構(gòu)體的尺寸。這里的結(jié)構(gòu)體的尺寸分別是考慮小于等于8字節(jié),小于等于16字節(jié),大于16字節(jié)。而結(jié)構(gòu)體成員類型則分為:全部都是非浮點數(shù)據(jù)成員、全部都是浮點數(shù)成員(這里會區(qū)分單精度和雙精度)、以及混合類型的成員(如果結(jié)構(gòu)體中有單精度和雙精度都算混合)。這樣一共分為9種情,下面就是針對結(jié)構(gòu)體類型返回的規(guī)則:

R3:針對非浮點數(shù)據(jù)成員的結(jié)構(gòu)體來說如果結(jié)構(gòu)體的尺寸<=8,那么結(jié)構(gòu)體的值會保存到X0, 如果尺寸<=16,那么保存到X0,X1中,如果尺寸>16則結(jié)構(gòu)體返回會保存到X8寄存器所指向的內(nèi)存中,也就是X8寄存器比較特殊,專門用來保存返回的結(jié)構(gòu)體的指針。

R4: 如果結(jié)構(gòu)體的成員都是單精度并且數(shù)量<=4 則返回結(jié)構(gòu)體的每個成員分別保存到S0,S1,S2, S3四個寄存中,如果結(jié)構(gòu)體成員數(shù)量超過4個則結(jié)構(gòu)體返回會保存到X8寄存器所指向的內(nèi)存中。

R5: 如果結(jié)構(gòu)體的成員都是雙精度并且數(shù)量<=4 則返回結(jié)構(gòu)體的每個成員分別保存到D0,D1,D2,D3四個寄存器中,如果結(jié)構(gòu)體成員數(shù)量超過4個則結(jié)構(gòu)體返回會保存到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)體返回會保存到X8寄存器所指向的內(nèi)存中。

下面演示幾個結(jié)構(gòu)體定義以及返回結(jié)構(gòu)體的函數(shù):

//長度為16字節(jié)的結(jié)構(gòu)體
struct S1
{
   char a;
   char b;
   double c;
};

 //長度超過16字節(jié)的混合成員結(jié)構(gòu)體
struct S2
{
   int a;
   int b;
   int c;
   double d;
};

//長度小于等于8字節(jié)的結(jié)構(gòu)體
struct S3
{
   int a;
   int b;
};


CGRect  foo1()
{
      //高級語言實現(xiàn)的返回
      return CGRectMake(10,20,30,40);
     //機器指令的函數(shù)返回的偽代碼如下:
    /* 
      D0 = 10
      D1 = 20
      D2 = 30
      D3 = 40
     ret
     */
}

struct S1 foo2()
{
    //高級語言實現(xiàn)的返回
    return (struct S1){10, 20, 30};
   //機器指令的函數(shù)返回的偽代碼如下:
    /* 
      X0 = 10 |  20 << 8
      X1 = 30
     ret
     */

}

struct S2 foo3()
{
   //高級語言實現(xiàn)的返回
   return (struct S2){10, 20, 30, 40};
  //機器指令的函數(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()
{
   //高級語言實現(xiàn)的返回
   return (struct S3){20, 30};
  //機器指令的函數(shù)返回的偽代碼如下:
  /*
        X0 = 20 | 30 << 32
        ret
  */
}

從上面的代碼可以看出來在x86_64/arm32兩種體系結(jié)構(gòu)下如果返回的類型是結(jié)構(gòu)體并且滿足特定要求時,系統(tǒng)會將結(jié)構(gòu)體指針當做函數(shù)的第一個參數(shù),而將源代碼中的第一個參數(shù)傳遞的寄存器往后移動,而在arm64位系統(tǒng)中則x8寄存器專門負責處理返回值為特殊結(jié)構(gòu)體的情況。

六、談?wù)刼bjc_msgSend系列函數(shù)

所有的OC方法最終都會通過objc_msgSend系列函數(shù)進行調(diào)用。這個函數(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ù)的差別主要是針對返回類型的不同而使用不同的消息發(fā)送函數(shù)。

從上述的函數(shù)返回值規(guī)則可以看對于long double 類型的函數(shù)返回在x86_64位系統(tǒng)的處理方式比較特殊,其返回的值將保存在特定的浮點堆棧寄存器中,所以objc_msgSend_fpret函數(shù)只用在x86_64位系統(tǒng)中返回類型為long double的OC方法的消息分發(fā)中,其他體系結(jié)構(gòu)都不會用到這個函數(shù)。同樣因為C99中引入了復(fù)數(shù)類型 _Complex 關(guān)鍵字,所以針對這種類型的 long double 返回會使用objc_msgSend_fp2ret函數(shù)。

從上述的函數(shù)的返回值規(guī)則還可以看出對于結(jié)構(gòu)體返回,如果結(jié)構(gòu)體尺寸大于一定的閾值后,x86_64位系統(tǒng)和arm32位系統(tǒng)都會將返回的結(jié)構(gòu)體轉(zhuǎn)化為第一個參數(shù)來進行傳遞,這樣就會使得真實的參數(shù)傳遞的寄存器往后順延,而arm64則直接只用x8寄存器來保存大于閾值的結(jié)構(gòu)體指針且并不會影響到參數(shù)的傳遞順序。因此除了arm64位系統(tǒng)外其他體系結(jié)構(gòu)系統(tǒng)中針對那些返回結(jié)構(gòu)體大于一定閾值的OC方法將使用objc_msgSend_stret函數(shù)進行消息分發(fā)。

上述的函數(shù)返回規(guī)則對 中的其他函數(shù)也是同樣適用的。


針對函數(shù)的調(diào)用、參數(shù)傳遞、函數(shù)的返回值的介紹規(guī)則就是這些了,當然這些規(guī)則除了對普通函數(shù)適用外對OC類方法也是同樣適用的。至于一個函數(shù)內(nèi)部應(yīng)該怎樣實現(xiàn),其實也是有一定的規(guī)則的。通過這些規(guī)則你可以了解到函數(shù)是如何跟棧內(nèi)存結(jié)合在一起的,以及函數(shù)調(diào)用棧是如何被構(gòu)造出來的,你還可以了解為什么一些函數(shù)調(diào)用不會出現(xiàn)在調(diào)用棧中等等相關(guān)的知識,以及可變參數(shù)函數(shù)內(nèi)部是如何實現(xiàn)的等等這部分的詳細介紹將會在: 深入iOS系統(tǒng)底層之函數(shù)(二):實現(xiàn) 進行深入的探討。

七、參考

blog.csdn.net/q_l_s/artic…

developer.apple.com/library/arc…

armv8,armv7, x86_64位系統(tǒng)CPU手冊

blog.sina.com.cn/s/blog_8619…

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/7070.html

相關(guān)文章

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

    摘要:序一直想寫一些關(guān)于系統(tǒng)底層方面的知識點,并且醞釀了很久,后來也跟其他人交流,你為何不出一個系列呢不必要一次性把所有的東西都寫完后才發(fā)表,我聽說后覺得非常的有道理,雖然自己的水平也很一般,但是想想自己還是有一些積累的。序 一直想寫一些關(guān)于系統(tǒng)底層方面的知識點,并且醞釀了很久,后來也跟其他人交流,你為何不出一個系列呢? 不必要一次性把所有的東西都寫完后才發(fā)表,我聽說后覺得非常的有道理,雖然自己的...

    番茄西紅柿 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<