關(guān)于objc_runtime的消息機(jī)制(一)

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections.
計(jì)算機(jī)科學(xué)中,任何問題都可以通過增加一層抽象(間接尋址)來實(shí)現(xiàn),當(dāng)然除了間接問題太多。 英文水平比較渣 ,只能翻譯成到這種'只可意會不可言傳'的程度。

那我們從直接尋址開始說起。內(nèi)存就是一維數(shù)組,數(shù)據(jù)和代碼片段存在其中,要找到這些數(shù)據(jù)和代碼片段 (數(shù)據(jù)和代碼片段只是人為的劃分) 只需要知道它所處位置的地址(編號)即可。代碼中的變量名和函數(shù)名在經(jīng)過編譯和鏈接后轉(zhuǎn)換成對應(yīng)的內(nèi)存地址(編號)。c語言中就是這樣的:

int add(int a,int b)
{
    return a + b;
}
typedef int (*FUNC_POINTER)(int,int);
int main(int argc, const char * argv[]) {
    
    
    long fun_address = (long)add;
    
    printf("函數(shù)地址為:%ld\n",fun_address);
    
    FUNC_POINTER f_p = (FUNC_POINTER)fun_address;
    printf("1 + 2 = %d\n",(*f_p)(1,2));
    
    
    printf("Hello, World!\n");
    return 0;
}
1.jpeg

運(yùn)行的結(jié)果很直白,我們將 add(函數(shù)名)轉(zhuǎn)換成long型后賦值給另一個(gè)長整型變量并打印,然后我們將這個(gè)長整型變量再轉(zhuǎn)換成函數(shù)指針(地址 編號)賦值給另一個(gè)指針變量。最后通過這個(gè)指針(地址 編號)成功調(diào)用了我們定義的add函數(shù)。很直接吧,直接奔著指針(地址 編號)去。
接下來我們談一個(gè)初級的"間接"問題。如何實(shí)現(xiàn)一個(gè)結(jié)構(gòu)體。我們有一萬個(gè)理由去使用結(jié)構(gòu)體。比如說 有些數(shù)據(jù)就是要放在一起才有意義 ;放在一起方便處理:記錄學(xué)生各科成績數(shù)據(jù),如果沒有結(jié)構(gòu)體,我們可能需要很多個(gè)一維數(shù)組,每個(gè)數(shù)組按照序號儲存學(xué)生的單科成績,這時(shí)候如果遇到需要排序的功能要求,那就相當(dāng)?shù)疤哿?,需要調(diào)整N多個(gè)數(shù)組。很容易出差錯(cuò);......等等諸如此類的原因吧。還是一個(gè)很簡單的小栗子:

struct Scores
{
    int math;
    int English;
    int physics;
};
typedef struct Scores Student;
int main(int argc, const char * argv[]) {
    // insert code here...
    Student zhangyu;
    zhangyu.math    = 100;
    zhangyu.english = 66;
    zhangyu.physics = 100;
    int * score = &zhangyu;
    printf("math score    = %d\n",*score);
    printf("english score = %d\n",*(score+1));
    printf("physics score = %d\n",*(score+2));
    printf("Hello, World!\n");
    return 0;
}
2.jpeg

(這里只列了幾個(gè)成績,絕對只是因?yàn)槲覒?,個(gè)人相當(dāng)不贊成用成績來衡量一個(gè)人的全部,即使是學(xué)生。)既然變量名可以被轉(zhuǎn)換為地址,結(jié)構(gòu)體中的成員聚在一塊,我們有了這么一大塊內(nèi)存的地址,自然也就很方便的可以找到各個(gè)成員的具體地址(指針 編號)了。代碼中我們就是這么干的,& 是C語言中取地址的符號,我們?nèi)〉昧私Y(jié)構(gòu)體變量的指針后賦值給了一個(gè)int型的指針變量,然后通過這個(gè)變量分別獲得了結(jié)構(gòu)體中第二個(gè)成員和第三個(gè)成員的內(nèi)容。這不就是間了個(gè)接么。

數(shù)據(jù)有放到一塊的需要,代碼段(函數(shù))自然也有需求。當(dāng)程序規(guī)模大了,很多的函數(shù),恐怕程序員就有點(diǎn)吃不消了。如果能將這些函數(shù)整理整理,分門別"類","類",“類” 該多好。這些個(gè)函數(shù)是跟鍵盤有關(guān)的,就先寫個(gè) KeyBoard(類名,告訴函數(shù)的使用者,接下來這些函數(shù)是跟KeyBoard相關(guān)的) 隨后用大括號將它們括起來。當(dāng)然了 為了清楚的說明這些情況,我們用Class關(guān)鍵字來開門見山的指明,我們要 分門別“類”了。

class KeyBoard
{
  void func1();
  void func2();
  void func3();
  ......
  int a;
  int b;
  int c;
};

此時(shí) 我們將三個(gè)函數(shù)和三個(gè)成員變量給聚到一塊去了,分門別了個(gè)“類”。函數(shù)要怎么處理呢?函數(shù)能不能也想變量那樣,簡單給湊合湊合捆綁在一塊呢,然后根據(jù)偏移來計(jì)算地址呢?似乎不那么容易吧。結(jié)構(gòu)體是由基本數(shù)據(jù)類型組成的,而基本數(shù)據(jù)類型的大?。ㄔ趦?nèi)存中所占的字節(jié)數(shù))是固定的。這顯然在計(jì)算偏移的時(shí)候是容易的。而代碼段就不那么容易了。稍加思索,可以想到,如果我們給函數(shù)名加上個(gè)標(biāo)記,一個(gè) “類”中的函數(shù)都有著同樣的標(biāo)記,至于函數(shù)地址嘛,還是該怎么樣就怎么樣。函數(shù)名到地址間的映射由編譯器來為我們完成。于是Student中的sleep函數(shù)可以被重命名為Student_sleep,Principal(校長)的sleep函數(shù)可以被重命名為Principal_sleep。同樣是睡覺 校長的“睡姿”跟你可是完全不同的。接下來還有一個(gè)問題,這些類中也有成員變量,類中的函數(shù)要處理成員變量,同一個(gè)類所用的成員函數(shù)是一樣的,可是處理的變量肯定不能相同,如果Principal類有個(gè)成員變量 bed,當(dāng)某個(gè)具體的Principal對象 (王校長)在執(zhí)行sleep的時(shí)候,他就得睡自個(gè)兒的床,睡別人的那是隔壁老王。要找到自個(gè)兒的成員變量,就需要自己的變量地址。于是每一個(gè)類的成員函數(shù)都在原型的基礎(chǔ)上多加一個(gè)參數(shù) Principal_sleep(Principal * p)。這個(gè)參數(shù)為編譯器自動添加。完全是不知不覺得。
說到這里,還得再多說一個(gè),c++的虛函數(shù)。之所以要提虛函數(shù)。是因?yàn)槊嫦驅(qū)ο蟮奶卣鞑粌H有剛剛說的封裝,還有多態(tài),多態(tài)是因?yàn)槔^承。我們單獨(dú)說這個(gè)多態(tài)。大概是要解決這樣的問題,我們設(shè)計(jì)好了某個(gè)“類”,有個(gè)類似的功能模塊 大部分跟已有的這個(gè)類很像,然而有個(gè)別的行為(方法,函數(shù))有些不同,我不想再寫一遍了。想聲明一下,除了某幾個(gè)方法是不一樣的,其余的沿用。另外我還想實(shí)現(xiàn),原有類的指針可以指向自己的對象也可以指向新類的對象,通過指針去調(diào)用兩個(gè)類中同名函數(shù)時(shí),根據(jù)指針?biāo)赶虻木唧w對象的不同,調(diào)用不同的函數(shù)(方法)。說的有點(diǎn)亂了(似乎跳過了繼承去說多態(tài),不太合適,我盡力吧)。認(rèn)真讀完上一小節(jié)的朋友,可能會說,那不是一樣的么,不同的類的函數(shù)有不同的前綴,根據(jù)變量類型的不同,分別去調(diào)用唄。但是思考的深一點(diǎn)就會想到,變量類型是由編譯器在編譯的時(shí)候?yàn)槲覀儽4娴?,編譯完的代碼中可不包括這樣的信息。運(yùn)行中的機(jī)器碼,兩個(gè)指針無非就是兩個(gè)不同的長整型數(shù)據(jù)而已(長整型一般等于CPU的字長)。通過指針我們僅能獲得不同對象的地址而已,普通的對象(結(jié)構(gòu)體)中只保存了成員標(biāo)量。如果我們能多保存一些幫助我們找到適當(dāng)函數(shù)的信息就好了。實(shí)際上 前文已經(jīng)提及,有了函數(shù)片段的地址(指針),就可以進(jìn)行調(diào)用了。因?yàn)橐粋€(gè)類的虛函數(shù)不止一個(gè),所以我們想象下,有一張?zhí)摵瘮?shù)地址的表(也就是表,或者聯(lián)想為數(shù)組),如果有了這個(gè)數(shù)組的首地址,我們就可以找到所有這些數(shù)據(jù)了。于是,在對象內(nèi)存的前面,留出8(32位機(jī)器是4字節(jié))個(gè)字節(jié)用來保存這個(gè)表地址。


4.jpeg

對象的內(nèi)存空間大約如圖這個(gè)樣子,本人很難,圖是網(wǎng)上截取的。接下來我們貼一段代碼驗(yàn)證一下這個(gè)事實(shí)。

#include <iostream>
using namespace std;
class Parent
{
public:
    virtual void func1()
    {
        cout << "this is Parent::fun1" <<endl;
    }
    virtual void func2()
    {
        cout << "this is Parent::fun2" <<endl;
    }
};
class Son:public Parent
{
public:
    virtual void func1()
    {
        cout << "this is Son::fun1" <<endl;
    }
    virtual void func2(int a,int b)
    {
        cout << "this is Son::fun2" <<endl;
    }
};
typedef void (*P_FUN)(void);

int main(int argc, const char * argv[]) {
    // insert code here...
    
    ////// 多態(tài)
    Parent * p =  new Parent;
    p->func1();

    Parent * p2 =  new Son;
    p2->func1();

    /////// 多態(tài)
    
    
    ///////虛函數(shù)表
    Son p3;
    cout << "虛函數(shù)表地址:" << (long*)(&p3) << endl;
    cout << "虛函數(shù)表 — 第一個(gè)函數(shù)地址:" <<(long*)*(long*)(&p3) << endl;

    P_FUN pFun;
    pFun = (P_FUN)*((long*)*(long*)(&p3));
    pFun();
    ///////虛函數(shù)表
    
    std::cout << "Hello, World!\n";
    return 0;
}

6.jpeg

運(yùn)行結(jié)果如圖,當(dāng)我們通過不同的指針去調(diào)用時(shí),將會調(diào)用不同的函數(shù),這是編譯器幫我們完成的多態(tài)。接下來我們通過去除對象內(nèi)存空間中前八個(gè)字節(jié)的內(nèi)容,然后找到了相應(yīng)的虛函數(shù)表的位置。也成功的調(diào)用了函數(shù)。
讀者可自行嘗試獲得Son::func2的地址并調(diào)用。
沒辦法 本來打算一片寫完的,結(jié)果吃完晚飯寫到現(xiàn)在 才寫了這么點(diǎn)。至于objc_runtime的多態(tài) 運(yùn)行時(shí)綁定 只能下回分解了。

不要懷疑 ,文章中的所有錯(cuò)別字都是我故意的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.面向?qū)ο蟮某绦蛟O(shè)計(jì)思想是什么? 答:把數(shù)據(jù)結(jié)構(gòu)和對數(shù)據(jù)結(jié)構(gòu)進(jìn)行操作的方法封裝形成一個(gè)個(gè)的對象。 2.什么是類?...
    少帥yangjie閱讀 5,125評論 0 14
  • 1. 結(jié)構(gòu)體和共同體的區(qū)別。 定義: 結(jié)構(gòu)體struct:把不同類型的數(shù)據(jù)組合成一個(gè)整體,自定義類型。共同體uni...
    breakfy閱讀 2,273評論 0 22
  • 我在想了半夜之后,還是沒有想通如何可以快速的與他人配合默契、反應(yīng)靈敏。你一句話幾個(gè)意思,誰知道呢。事情已經(jīng)發(fā)生了,...
    糟糕小賴閱讀 186評論 0 1
  • 我從來沒有聽過靠吃飯減肥成功的。 我只聽過靠節(jié)食減肥的,或者運(yùn)動減肥的。但是節(jié)食和運(yùn)動這件事不能三天打魚兩天曬網(wǎng),...
    帥鹿是小太陽閱讀 315評論 0 1

友情鏈接更多精彩內(nèi)容