版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
從C的偽代碼到匯編-動手實現(xiàn)objc-msgSend網(wǎng)址:edu.51CTO.com從C的偽代碼到匯編,動手實現(xiàn)objc_msgSendobjc_msgSend函數(shù)支撐了我們使用Objective-C實現(xiàn)的一切。GwynneRaskind,F(xiàn)ridayQ&A的讀者,建議我談?wù)刼bjc_msgSend的內(nèi)部實現(xiàn)。要理解某件事還有比自己動手實現(xiàn)一次更好的方法嗎?咱們來自己動手實現(xiàn)一個objc_msgSend。Tramapoline!Trampopoline!(蹦床)當(dāng)你寫了一個發(fā)送Objective-C消息的方法:[obj
message]
編譯器會生成一個objc_msgSend調(diào)用:objc_msgSend(obj,
@selector(message));
之后objc_msgSend會負(fù)責(zé)轉(zhuǎn)發(fā)這個消息。它都做了什么?它會查找合適的函數(shù)指針或者IMP,然后調(diào)用,最后跳轉(zhuǎn)。任何傳給objc_msgSend的參數(shù),最終都會成為IMP的參數(shù)。IMP的返回值成為了最開始被調(diào)用的方法的返回值。Class
c
=
object_getClass(self);
IMP
imp
=
class_getMethodImplementation(c,
_cmd);
return
imp(self,
_cmd,
...);
}
這有點過于簡單。事實上會有一個方法緩存來提升查找速度,像這樣:id
objc_msgSend(id
self,
SEL
_cmd,
...)
{
Class
c
=
object_getClass(self);
IMP
imp
=
cache_lookup(c,
_cmd);
if(!imp)
imp
=
class_getMethodImplementation(c,
_cmd);
return
imp(self,
_cmd,
...);
}
通常為了速度,cache_lookup使用inline函數(shù)實現(xiàn)。匯編在Apple版的runtime中,為了最大化速度,整個函數(shù)是使用匯編實現(xiàn)的。在Objective-C中每次發(fā)送消息都會調(diào)用objc_msgSend,在一個應(yīng)用中最簡單的動作都會有成千或者上百萬的消息。為了讓事情更簡單,我自己的實現(xiàn)中會盡可能少的使用匯編,使用獨立的C函數(shù)抽象復(fù)雜度。匯編代碼會實現(xiàn)下面的功能:id
objc_msgSend(id
self,
SEL
_cmd,
...)
{
IMP
imp
=
GetImplementation(self,
_cmd);
imp(self,
_cmd,
...);
}
GetImplementation
可以用更可讀的方式工作。
匯編代碼需要:1.把所有潛在的參數(shù)存儲在安全的地方,確保GetImplementation不會覆蓋它們。2.調(diào)用GetImplementation。3.把返回值保存在某處。4.恢復(fù)所有的參數(shù)值。5.跳轉(zhuǎn)到GetImplementation返回的IMP。讓我們開始吧!這里我會嘗試使用x86-64匯編,這樣可以很方便的在Mac上工作。這些概念也可以應(yīng)用于i386或者ARM。這個函數(shù)會保存在獨立的文件中,叫做msgsend-asm.s。這個文件可以像源文件那樣傳遞給編譯器,然后會被編譯并鏈接到程序中。第一件事要做的是聲明全局的符號(globalsymbol)。因為一些無聊的歷史原因,C函數(shù)的globalsymbol會在名字前有個下劃線:.globl
_objc_msgSend
_objc_msgSend:
編譯器會很高興的鏈接最近可使用的(nearestavailable)objc_msgSend。簡單的鏈接這個到一個測試app已經(jīng)可以讓[objmessage]表達(dá)式使用我們自己的代碼而不是蘋果的runtime,這樣可以相當(dāng)方便的測試我們的代碼確保它可以工作。整型數(shù)和指針參數(shù)會被傳入寄存器%rsi,%rdi,%rdx,%rcx,%r8和%r9。其他類型的參數(shù)會被傳進棧(stack)中。這個函數(shù)最先做的事情是把這六個寄存器中的值保存在棧中,這樣它們可以在之后被恢復(fù):pushq
%rsi
pushq
%rdi
pushq
%rdx
pushq
%rcx
pushq
%r8
pushq
%r9
除了這些寄存器,寄存器%rax扮演了一個隱藏的參數(shù)。它用于變參的調(diào)用,并保存?zhèn)魅氲南蛄考拇嫫鳎╲ectorregisters)的數(shù)量,用于被調(diào)用的函數(shù)可以正確的準(zhǔn)備變參列表。以防目標(biāo)函數(shù)是個變參的方法,我同樣也保存了這個寄存器中的值:pushq
%rax
為了完整性,用于傳入浮點類型參數(shù)的寄存器%xmm也應(yīng)該被保存。但是,要是我能確保GetImplementation不會傳入任何的浮點數(shù),我就可以忽略掉它們,這樣我就可以讓代碼更簡潔。接著,對齊棧。MacOSX要求一個函數(shù)調(diào)用棧需要對齊16字節(jié)邊界。上面的代碼已經(jīng)是棧對齊的,但是還是需要顯式手動處理下,這樣可以確保所有都是對齊的,就不用擔(dān)心動態(tài)調(diào)用函數(shù)時會崩潰。要對齊棧,在保存%r12的原始值到棧中后,我把當(dāng)前的棧指針保存到了%r12中。%r12是隨便選的,任何保存的調(diào)用者寄存器(caller-savedregister)都可以。重要的是在調(diào)用完GetImplementation后這些值仍然存在。然后我把棧指針按位與(and)上-0x10,這樣可以清除棧底的四位:pushq
%r12
mov
%rsp,
%r12
andq
$-0x10,
%rsp
現(xiàn)在棧指針是對齊的了。這樣可以安全的避開上面(above)保存的寄存器,因為棧是向下增長的,這種對齊的方法會讓它更向下(moveitfurtherdown)。是時候該調(diào)用GetImplementation了。它接收兩個參數(shù),self和_cmd。調(diào)用習(xí)慣是把這兩個參數(shù)分別保存到%rsi和%rdi中。然而傳入objc_msgSend中時就是那樣了,它們沒有被移動過,所以不需要改變它們。所有要做的事情實際上是調(diào)用GetImplementation,方法名前面也要有一個下劃線:callq
_GetImplementation
整型數(shù)和指針類型的返回值保存在%rax中,這就是找到返回的IMP的地方。因為%rax需要被恢復(fù)到初始的狀態(tài),返回的IMP需要被移動到別的地方。我隨便選了個%r11。mov
%rax,
%r11
現(xiàn)在是時候該恢復(fù)原樣了。首先要恢復(fù)之前保存在%r12中的棧指針,然后恢復(fù)舊的%r12的值:mov
%r12,
%rsp
popq
%r12
然后按壓入棧的相反順序恢復(fù)寄存器的值:popq
%rax
popq
%r9
popq
%r8
popq
%rcx
popq
%rdx
popq
%rdi
popq
%rsi
現(xiàn)在一切都已經(jīng)準(zhǔn)備好了。參數(shù)寄存器(argumentregisters)都恢復(fù)到了之前的樣子。目標(biāo)函數(shù)需要的參數(shù)都在合適的位置了。IMP在寄存器%r11中,現(xiàn)在要做的是跳轉(zhuǎn)到那里:jmp
*%r11
就這樣!不需要其他的匯編代碼了。jump把控制權(quán)交給了方法實現(xiàn)。從代碼的角度看,就好像發(fā)送消息者直接調(diào)用的這個方法。之前的那些迂回的調(diào)用方法都消失了。當(dāng)方法返回,它會直接放回到objc_msgSend的調(diào)用處,不需要其他的操作。這個方法的返回值可以在合適的地方找到。非常規(guī)的返回值有一些細(xì)節(jié)需要注意。比如大的結(jié)構(gòu)體(不能用一個寄存器大小保存的返回值)。在x86-64,大的結(jié)構(gòu)體使用隱藏的第一個參數(shù)返回。當(dāng)你像這樣調(diào)用:NSRect
r
=
SomeFunc(a,
b,
c);
這個調(diào)用會被翻譯成這樣:NSRect
r;
SomeFunc(&r,
a,
b,
c);
用于返回值的內(nèi)存地址被傳入到%rdi中。因為objc_msgSend期望%rdi和%rsi中包含self和_cmd,當(dāng)一個消息返回大的結(jié)構(gòu)體時不會起作用的。同樣的問題存在于多個不同平臺上。runtime提供了objc_msgSend_stret用于返回結(jié)構(gòu)體,工作原理和objc_msgSend類似,只是知道在%rsi中尋找self和在%rdx中尋找_cmd。相似的問題發(fā)生在一些平臺上發(fā)送消息(messages)返回浮點類型值。在這些平臺上,runtime提供了objc_msgSend_fpret(在x86-64,objc_msgSend_fpret2用于特別極端的情況)。方法查找讓我們繼續(xù)實現(xiàn)GetImplementation。上面的匯編蹦床意味著這些代碼可以用C實現(xiàn)。記得嗎,在真正的runtime中,這些代碼都是直接用匯編寫的,是為了盡可能的保證最快的速度。這樣不僅可以更好的控制代碼,也可以避免重復(fù)像上面那樣保存并恢復(fù)寄存器的代碼。GetImplementation可以簡單的調(diào)用class_getMethodImplementation實現(xiàn),混入Objective-Cruntime的實現(xiàn)。這有點無聊。真正的objc_msgSend為了最大化速度首先會查找類的方法緩存。因為GetImplementation想模仿objc_msgSend,所以它也會這么做。要是緩存中不包含給定的selector入口點(entry),它會繼續(xù)查找runtime(itfallbacktoqueryingtheruntime)。我們現(xiàn)在需要的是一些結(jié)構(gòu)體定義。方法緩存是類(class)結(jié)構(gòu)體中的私有結(jié)構(gòu)體,為了得到它我們需要定義自己的版本。盡管是私有的,這些結(jié)構(gòu)體的定義還是可以通過蘋果的Objective-Cruntime開源實現(xiàn)獲得(譯注:/tarballs/objc4/)。首先需要定義一個cacheentry:typedef
struct
{
SEL
name;
void
*unused;
IMP
imp;
}
cache_entry;
相當(dāng)簡單。別問我unused字段是干什么的,我也不知道它為什么在那。這是cache的全部定義:struct
objc_cache
{
uintptr_t
mask;
uintptr_t
occupied;
cache_entry
*buckets[1];
};
緩存使用hashtable(哈希表)實現(xiàn)。實現(xiàn)這個表是為了速度的考慮,其他無關(guān)的都簡化了,所以它有點不一樣。表的大小永遠(yuǎn)都是2的冪。表格使用selector做索引,bucket是直接使用selector的值做索引,可能會通過移位去除不相關(guān)的低位(lowbits),并與mask執(zhí)行一個邏輯與(logicaland)。下面是一些宏,用于給定selector和mask時計算bucket的索引:#ifndef
__LP64__
#
define
CACHE_HASH(sel,
mask)
(((uintptr_t)(sel)>>2)
&
(mask))
#else
#
define
CACHE_HASH(sel,
mask)
(((unsigned
int)((uintptr_t)(sel)>>0))
&
(mask))
#endif
最后是類的結(jié)構(gòu)體。這是Class指向的類型:struct
class_t
{
struct
class_t
*isa;
struct
class_t
*superclass;
struct
objc_cache
*cache;
IMP
*vtable;
};
需要的結(jié)構(gòu)體都已經(jīng)有了,現(xiàn)在開始實現(xiàn)GetImplementation吧:IMP
GetImplementation(id
self,
SEL
_cmd)
{
首先要做的是獲取對象的類。真正的objc_msgSend通過類似self->isa的方式獲取,但是它會使用官方的API實現(xiàn):Class
c
=
object_getClass(self);
因為我想訪問最原始的形式,我會為指向class_t結(jié)構(gòu)體的指針執(zhí)行類型轉(zhuǎn)換:struct
class_t
*classInternals
=
(struct
class_t
*)c;
現(xiàn)在該查找IMP了。首先我們把它初始為NULL。如果我們在緩存中找到,我們會賦值為它。如果查找緩存后仍為NULL,我們會回退到速度較慢的方法:IMP
imp
=
NULL;
接著,獲取指向cache的指針:struct
objc_cache
*cache
=
classInternals->cache;
計算bucket的索引,獲取指向buckets數(shù)組的指針:uintptr_t
index
=
CACHE_HASH(_cmd,
cache->mask);
cache_entry
**buckets
=
cache->buckets;
然后,我們使用要找的selector查找緩存。runtime使用的是線性鏈(linearchaining),之后只是遍歷buckets子集直到找到需要的entry或者NULLentry:for(;
buckets[index]
!=
NULL;
index
=
(index
+
1)
&
cache->mask)
{
if(buckets[index]->name
==
_cmd)
{
imp
=
buckets[index]->imp;
break;
}
}
如果沒有找到entry,我們會調(diào)用runtime使用一種較慢的方法。在真正的objc_msgSend中,上面的所有代碼都是使用匯編實現(xiàn)的,這時候就該離開匯編代碼調(diào)用runtime自己的方法了。一旦查找緩存后沒有找到需要的entry,期望快速發(fā)送消息的希望就要落空了。這時候獲取更快的速度就沒那么重要了,因為已經(jīng)注定會變慢,在一定程度上也極少的需要這么調(diào)用。因為這點,放棄匯編代碼轉(zhuǎn)而使用更可維護的C也是可以接受的:if(imp
==
NULL)
imp
=
class_getMethodImplementation(c,
_cmd);
不管怎樣,IMP現(xiàn)在已經(jīng)獲取到了。如果它在緩存中,就會在那里找到它,否則它會通過runtime查找到。class_getMethodImplementation調(diào)用同樣會使用緩存,所以下次調(diào)用會更快。剩下的就是返回IMP:return
imp;
}
測試為了確保它能工作,我寫了一個快速的測試程序:@interface
Test
:
NSObject
-
(void)none;
-
(void)param:
(int)x;
-
(void)params:
(int)a
:
(int)b
:
(int)c
:
(int)d
:
(int)e
:
(int)f
:
(int)g;
-
(int)retval;
@end
@implementation
Test
-
(id)init
{
fprintf(stderr,
"in
init
method,
self
is
%p\n",
self);
return
self;
}
-
(void)none
{
fprintf(stderr,
"in
none
method\n");
}
-
(void)param:
(int)x
{
fprintf(stderr,
"got
parameter
%d\n",
x);
}
-
(void)params:
(int)a
:
(int)b
:
(int)c
:
(int)d
:
(int)e
:
(int)f
:
(int)g
{
fprintf(stderr,
"got
params
%d
%d
%d
%d
%d
%d
%d\n",
a,
b,
c,
d,
e,
f,
g);
}
-
(int)ret
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度個人房屋抵押民間借貸借款協(xié)議文本3篇
- 2025年度平板車租賃運輸合同風(fēng)險評估與控制
- 個人租房合同標(biāo)準(zhǔn)版可打印
- 二零二五年度處理男子外遇妻子懷孕離婚撫養(yǎng)權(quán)糾紛調(diào)解合同
- 二零二五年度智慧家居項目報建代理與智能家居合同2篇
- 2025年度鐵礦石進出口關(guān)稅及稅費繳納合同規(guī)范
- 二零二五版礦產(chǎn)資源租賃居間代理合同3篇
- 二零二五年度企事業(yè)單位廉潔從業(yè)監(jiān)督員聘任合同
- 2025阿里巴巴智能物流機器人研發(fā)及銷售合同3篇
- 重慶文化藝術(shù)職業(yè)學(xué)院《應(yīng)用文寫作》2023-2024學(xué)年第一學(xué)期期末試卷
- 《大學(xué)生職業(yè)發(fā)展與就業(yè)指導(dǎo)》課程標(biāo)準(zhǔn)
- 第23課《出師表》課件(共56張)
- GB/T 3953-2024電工圓銅線
- 發(fā)電機停電故障應(yīng)急預(yù)案
- 接電的施工方案
- 常用藥物作用及副作用課件
- 幼兒阿拉伯?dāng)?shù)字描紅(0-100)打印版
- 社會組織等級評估報告模板
- GB/T 12173-2008礦用一般型電氣設(shè)備
- 新媒體研究方法教學(xué)ppt課件(完整版)
- 2020新版?zhèn)€人征信報告模板
評論
0/150
提交評論