关于objc_runtime的消息机制(一)

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections.
计算机科学中,任何问题都可以通过增加一层抽象(间接寻址)来实现,当然除了间接问题太多。 英文水平比较渣 ,只能翻译成到这种'只可意会不可言传'的程度。

那我们从直接寻址开始说起。内存就是一维数组,数据和代码片段存在其中,要找到这些数据和代码片段 (数据和代码片段只是人为的划分) 只需要知道它所处位置的地址(编号)即可。代码中的变量名和函数名在经过编译和链接后转换成对应的内存地址(编号)。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("函数地址为:%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

运行的结果很直白,我们将 add(函数名)转换成long型后赋值给另一个长整型变量并打印,然后我们将这个长整型变量再转换成函数指针(地址 编号)赋值给另一个指针变量。最后通过这个指针(地址 编号)成功调用了我们定义的add函数。很直接吧,直接奔着指针(地址 编号)去。
接下来我们谈一个初级的"间接"问题。如何实现一个结构体。我们有一万个理由去使用结构体。比如说 有些数据就是要放在一起才有意义 ;放在一起方便处理:记录学生各科成绩数据,如果没有结构体,我们可能需要很多个一维数组,每个数组按照序号储存学生的单科成绩,这时候如果遇到需要排序的功能要求,那就相当蛋疼了,需要调整N多个数组。很容易出差错;......等等诸如此类的原因吧。还是一个很简单的小栗子:

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

(这里只列了几个成绩,绝对只是因为我懒,个人相当不赞成用成绩来衡量一个人的全部,即使是学生。)既然变量名可以被转换为地址,结构体中的成员聚在一块,我们有了这么一大块内存的地址,自然也就很方便的可以找到各个成员的具体地址(指针 编号)了。代码中我们就是这么干的,& 是C语言中取地址的符号,我们取得了结构体变量的指针后赋值给了一个int型的指针变量,然后通过这个变量分别获得了结构体中第二个成员和第三个成员的内容。这不就是间了个接么。

数据有放到一块的需要,代码段(函数)自然也有需求。当程序规模大了,很多的函数,恐怕程序员就有点吃不消了。如果能将这些函数整理整理,分门别"类","类",“类” 该多好。这些个函数是跟键盘有关的,就先写个 KeyBoard(类名,告诉函数的使用者,接下来这些函数是跟KeyBoard相关的) 随后用大括号将它们括起来。当然了 为了清楚的说明这些情况,我们用Class关键字来开门见山的指明,我们要 分门别“类”了。

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

此时 我们将三个函数和三个成员变量给聚到一块去了,分门别了个“类”。函数要怎么处理呢?函数能不能也想变量那样,简单给凑合凑合捆绑在一块呢,然后根据偏移来计算地址呢?似乎不那么容易吧。结构体是由基本数据类型组成的,而基本数据类型的大小(在内存中所占的字节数)是固定的。这显然在计算偏移的时候是容易的。而代码段就不那么容易了。稍加思索,可以想到,如果我们给函数名加上个标记,一个 “类”中的函数都有着同样的标记,至于函数地址嘛,还是该怎么样就怎么样。函数名到地址间的映射由编译器来为我们完成。于是Student中的sleep函数可以被重命名为Student_sleep,Principal(校长)的sleep函数可以被重命名为Principal_sleep。同样是睡觉 校长的“睡姿”跟你可是完全不同的。接下来还有一个问题,这些类中也有成员变量,类中的函数要处理成员变量,同一个类所用的成员函数是一样的,可是处理的变量肯定不能相同,如果Principal类有个成员变量 bed,当某个具体的Principal对象 (王校长)在执行sleep的时候,他就得睡自个儿的床,睡别人的那是隔壁老王。要找到自个儿的成员变量,就需要自己的变量地址。于是每一个类的成员函数都在原型的基础上多加一个参数 Principal_sleep(Principal * p)。这个参数为编译器自动添加。完全是不知不觉得。
说到这里,还得再多说一个,c++的虚函数。之所以要提虚函数。是因为面向对象的特征不仅有刚刚说的封装,还有多态,多态是因为继承。我们单独说这个多态。大概是要解决这样的问题,我们设计好了某个“类”,有个类似的功能模块 大部分跟已有的这个类很像,然而有个别的行为(方法,函数)有些不同,我不想再写一遍了。想声明一下,除了某几个方法是不一样的,其余的沿用。另外我还想实现,原有类的指针可以指向自己的对象也可以指向新类的对象,通过指针去调用两个类中同名函数时,根据指针所指向的具体对象的不同,调用不同的函数(方法)。说的有点乱了(似乎跳过了继承去说多态,不太合适,我尽力吧)。认真读完上一小节的朋友,可能会说,那不是一样的么,不同的类的函数有不同的前缀,根据变量类型的不同,分别去调用呗。但是思考的深一点就会想到,变量类型是由编译器在编译的时候为我们保存的,编译完的代码中可不包括这样的信息。运行中的机器码,两个指针无非就是两个不同的长整型数据而已(长整型一般等于CPU的字长)。通过指针我们仅能获得不同对象的地址而已,普通的对象(结构体)中只保存了成员标量。如果我们能多保存一些帮助我们找到适当函数的信息就好了。实际上 前文已经提及,有了函数片段的地址(指针),就可以进行调用了。因为一个类的虚函数不止一个,所以我们想象下,有一张虚函数地址的表(也就是表,或者联想为数组),如果有了这个数组的首地址,我们就可以找到所有这些数据了。于是,在对象内存的前面,留出8(32位机器是4字节)个字节用来保存这个表地址。


4.jpeg

对象的内存空间大约如图这个样子,本人很难,图是网上截取的。接下来我们贴一段代码验证一下这个事实。

#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...
    
    ////// 多态
    Parent * p =  new Parent;
    p->func1();

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

    /////// 多态
    
    
    ///////虚函数表
    Son p3;
    cout << "虚函数表地址:" << (long*)(&p3) << endl;
    cout << "虚函数表 — 第一个函数地址:" <<(long*)*(long*)(&p3) << endl;

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

6.jpeg

运行结果如图,当我们通过不同的指针去调用时,将会调用不同的函数,这是编译器帮我们完成的多态。接下来我们通过去除对象内存空间中前八个字节的内容,然后找到了相应的虚函数表的位置。也成功的调用了函数。
读者可自行尝试获得Son::func2的地址并调用。
没办法 本来打算一片写完的,结果吃完晚饭写到现在 才写了这么点。至于objc_runtime的多态 运行时绑定 只能下回分解了。

不要怀疑 ,文章中的所有错别字都是我故意的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容

  • 1.面向对象的程序设计思想是什么? 答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 2.什么是类?...
    少帅yangjie阅读 4,998评论 0 14
  • 1. 结构体和共同体的区别。 定义: 结构体struct:把不同类型的数据组合成一个整体,自定义类型。共同体uni...
    breakfy阅读 2,123评论 0 22
  • 我在想了半夜之后,还是没有想通如何可以快速的与他人配合默契、反应灵敏。你一句话几个意思,谁知道呢。事情已经发生了,...
    糟糕小赖阅读 161评论 0 1
  • 我从来没有听过靠吃饭减肥成功的。 我只听过靠节食减肥的,或者运动减肥的。但是节食和运动这件事不能三天打鱼两天晒网,...
    帅鹿是小太阳阅读 273评论 0 1