为什么objc_msgSend必须用汇编实现
譯者前言
?
總是看到有人說(shuō)用匯編實(shí)現(xiàn)objc_msgSend是為了速度快,當(dāng)然這個(gè)不可否認(rèn)。但是難道沒(méi)有別的原因?于是就看到了這篇文章,遂翻譯之!=。=
?
我自己的理解就是,用匯編實(shí)現(xiàn),是為了應(yīng)對(duì)不同的“Calling convention”,把函數(shù)調(diào)用前的棧和寄存器的參數(shù)、狀態(tài)設(shè)置,交給編譯器去處理。
?
先看看原文吧。
?
原作者: Ari Grant
原文鏈接: Why objc_msgSend Must be Written in Assembly
http://arigrant.com/blog/2014/2/12/why-objcmsgsend-must-be-written-in-assembly
?
開(kāi)始
?
對(duì)于Objective-C來(lái)說(shuō),調(diào)用一個(gè)對(duì)象實(shí)例的方法,也叫作向這個(gè)對(duì)象實(shí)例“發(fā)送消息”,而每條“消息”,在編譯階段都會(huì)轉(zhuǎn)變?yōu)橐淮螌?duì)objc_msgSend函數(shù)的調(diào)用,調(diào)用的參數(shù)不僅有原本消息的所有參數(shù),還有消息的接收者receiver和對(duì)應(yīng)的方法selector。舉個(gè)例子,下面的語(yǔ)句:
?
[receiver message:foo beforeDate:bar];
?
將會(huì)被編譯成:
?
objc_msgSend(receiver, @selector(message:beforeDate:), foo, bar);
?
對(duì)于objc_msgSend函數(shù)的實(shí)現(xiàn)原理,前人已經(jīng)做了大量的探索。所以,本文將會(huì)把重點(diǎn)放在objc_msgSend的一個(gè)之前沒(méi)有太受到關(guān)注的點(diǎn)上,那就是:
?
objc_msgSend是不可能用Objective-C、C或者C++實(shí)現(xiàn)的。
?
THE RETURN TYPE – 返回類(lèi)型
?
先看看如下兩行代碼:
?
NSUInteger n = [array count];
id obj = [array objectAtIndex:6];
?
直觀上看,將會(huì)被編譯成
?
NSUInteger n = objc_msgSend(array,??@selector(count));
id obj = objc_msgSend(array, @selector(objectAtIndex:), 6);
?
但是實(shí)際上這是不可能的,因?yàn)闆](méi)有函數(shù)可以同時(shí)滿足這兩個(gè)調(diào)用。而且它的返回值也不能同時(shí)是NSUInteger和id。
?
而且,上面的代碼也是無(wú)法編譯通過(guò)的。那么,加上類(lèi)型轉(zhuǎn)換怎么樣?
?
NSUInteger n = (NSUInteger (*)(id, SEL))objc_msgSend(array,??@selector(count));
id obj = (id (*)(id, SEL, NSUInteger))objc_msgSend(array, @selector(objectAtIndex:), 6);
?
這下可以編譯通過(guò)了,雖然看起來(lái)不直觀。。。
objc_msgSend是一個(gè)Public的函數(shù),在里聲明,如果你想直接調(diào)用它,就必須按照上面的格式加上強(qiáng)制類(lèi)型轉(zhuǎn)換,要不然是無(wú)法編譯通過(guò)的。但是objc_msgSend到底是如何實(shí)現(xiàn),來(lái)支持各種返回類(lèi)型的?本文后面會(huì)講到。
?
THE IMP – 方法對(duì)應(yīng)的函數(shù)指針
?
objc_msgSend函數(shù)的本質(zhì)很簡(jiǎn)單,傳入一個(gè)接受者對(duì)象實(shí)例receiver和方法名selector,它就會(huì)按照以下步驟執(zhí)行:(譯者注:只是最粗略的步驟=。=)
?
-
獲取receiver得類(lèi)Class
-
在Class的方法列表method table里面查找對(duì)應(yīng)selector的方法實(shí)現(xiàn)
-
找到的話就調(diào)用,返回
-
找不到就在其父類(lèi)中找,重復(fù)前面的步驟(直到?jīng)]有父類(lèi)為止)
?
整個(gè)流程很簡(jiǎn)單,沿著繼承鏈,向上找到方法selector對(duì)應(yīng)的函數(shù)指針即可,也就是IMP。同時(shí),在每層Class中都有緩存,加快后續(xù)的方法查找。但是,這也只是objc_msgSend的實(shí)現(xiàn)細(xì)節(jié),所以,接著往下看。
?
THE ARG TYPES AND COUNT – 參數(shù)類(lèi)型和數(shù)量
?
簡(jiǎn)單來(lái)說(shuō),當(dāng)objc_msgSend找到對(duì)應(yīng)的函數(shù)指針后,只要用傳入的參數(shù)調(diào)用這個(gè)函數(shù)即可。剩下來(lái)的就是找到一種方法,可以調(diào)用任意參數(shù)類(lèi)型、數(shù)量的任意函數(shù)。
?
參數(shù)的數(shù)量很容易計(jì)算。然后我們可以把所有的參數(shù)都放入varargs,然后調(diào)用函數(shù)時(shí)傳入即可。但是這樣的話,每個(gè)Objective-C的方法都必須在其prologue(譯者注:函數(shù)執(zhí)行具體的“任務(wù)”前,所做的準(zhǔn)備環(huán)節(jié))里面把所有的參數(shù)從varargs里面提取出來(lái)。
?
這種把參數(shù)打包到varargs里面然后又取出來(lái)的辦法顯然是非常糟糕的,同時(shí)也是不必要的。
?
在C語(yǔ)言中,調(diào)用一個(gè)函數(shù)會(huì)被編譯成對(duì)應(yīng)的匯編語(yǔ)言指令,首先是設(shè)置參數(shù)(把參數(shù)放到寄存器、棧上),然后用如jump或者call的指令,跳到具體的函數(shù)代碼地址處。如果我們想支持任意類(lèi)型的函數(shù)類(lèi)型,我們就必須寫(xiě)一個(gè)switch語(yǔ)句,把所有的參數(shù)組合情況都包含起來(lái),這樣才能正確的為任何形式的函數(shù)設(shè)置參數(shù)(譯者注:即按照某種“規(guī)范”、“約定”,把參數(shù)依次存放到“約定”的寄存器、棧上),這顯然是沒(méi)有擴(kuò)展性的,更是不可能的。
?
UNWINDING THE CALL – 拆解調(diào)用
?
objc_msgSend的解決辦法,主要依據(jù)的是:當(dāng)objc_msgSend被調(diào)用時(shí),所有的參數(shù)已經(jīng)被設(shè)置好了。
?
換一種方式來(lái)說(shuō),就是:在objc_msgSend開(kāi)始執(zhí)行時(shí),棧幀(stack frame)的狀態(tài)、數(shù)據(jù),和各個(gè)寄存器的組合形式、數(shù)據(jù),跟調(diào)用具體的函數(shù)指針(IMP)時(shí)所需的狀態(tài)、數(shù)據(jù),是完全一致的!
?
如下這行代碼:
?
id obj = objc_msgSend(array, @selector(objectAtIndex:), 6);
?
在調(diào)用objc_msgSend時(shí),需要設(shè)置三個(gè)參數(shù),分別是被調(diào)用方receiver、方法名selector和最后一個(gè)整型參數(shù)6。這和具體的方法函數(shù)IMP的參數(shù)順序、類(lèi)型是完全一致的,也就是說(shuō),調(diào)用objc_msgSend前,設(shè)置的棧、寄存器的狀態(tài)、數(shù)據(jù)正是調(diào)用具體的方法函數(shù)時(shí)需要的狀態(tài)!
?
所以,當(dāng)objc_msgSend找到要調(diào)用的函數(shù)實(shí)現(xiàn)IMP后,只需要把所有的對(duì)棧、寄存器的操作“倒”回到objc_msgSend執(zhí)行開(kāi)始的狀態(tài)(類(lèi)似于函數(shù)執(zhí)行完成return返回前,做的“收尾處理”工作一樣,即epilogue),直接jump/call到IMP函數(shù)指針對(duì)應(yīng)的地址,執(zhí)行指令即可,因?yàn)樗械膮?shù)已經(jīng)被設(shè)置好了。
?
同時(shí),當(dāng)selector對(duì)應(yīng)的IMP執(zhí)行完成后,返回值也被正確的設(shè)置好了(在x86平臺(tái)上,返回值被設(shè)置到了指定的寄存器eax/rax里,在arm上,則是r0寄存器),所以,我們也不必?fù)?dān)心前文提到的不同類(lèi)型的返回值問(wèn)題了。
?
WRAP UP – 總結(jié)
?
把上面提到的所有解釋綜合起來(lái),就是:在C語(yǔ)言里面調(diào)用函數(shù),必須在編譯時(shí)就知道調(diào)用的“狀態(tài)”;而這些“狀態(tài)”在運(yùn)行時(shí)是無(wú)法得出或正確處理的,所以必須往底層走,用匯編處理。(譯者注:這里不知道咋翻譯好=。=,原文是:calling a function in C requires the signature to be known for each call-site at compile-time;doing so at run-time is not possible and so one must drop down into assembly and party there instead.)
?
UPDATE – 后續(xù)
?
有人指出objc_msgSend有可能是用GCC的擴(kuò)展方法__builtin_apply_args,__builtin_apply,和__builtin_return實(shí)現(xiàn)的。這也正指出了一個(gè)事實(shí),就是這些builtins方法是非常有必要的,因?yàn)閱慰空Z(yǔ)言本身無(wú)法實(shí)現(xiàn)這些功能。實(shí)現(xiàn)objc_msgSend所需要的技巧,也正是實(shí)現(xiàn)這些builtins方法所需要的技巧。本文的目的并不是非要將什么是真正的C、什么不是真正的C分個(gè)清楚,只是為了指出objc_msgSend特殊罷了。
?
譯者總結(jié)
?
開(kāi)頭也說(shuō)了,我的理解是:用匯編實(shí)現(xiàn),是為了應(yīng)對(duì)不同的“Calling convention”,把函數(shù)調(diào)用前的棧和寄存器的參數(shù)、狀態(tài)設(shè)置,交給編譯器去處理。
?
嗯,以后不要再說(shuō)用匯編實(shí)現(xiàn)只是為了快了=。=
轉(zhuǎn)載于:https://www.cnblogs.com/fengmin/p/5619115.html
總結(jié)
以上是生活随笔為你收集整理的为什么objc_msgSend必须用汇编实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux板级初始化
- 下一篇: 图片文字识别教程