美文网首页
08-this的本质

08-this的本质

作者: ducktobey | 来源:发表于2019-11-09 16:24 被阅读0次

this

现在有以下一段代码程序

struct Person {
    int m_age;
    void run() {
        cout << "Person :: run() " << m_age << endl;
    }
};

int main() {
    Person person;
    person.m_age = 10;
    person.run();
    getchar();
    return 0;
}

这段程序,我们都很清楚,首先我们创建了一个person对象,然后为对象的m_age成员变量赋了一个初始值,最后调用person对象的run函数。并且最终的打印结果是Person :: run() 10,相信各位读者是可以理解的。但是,这里有一个问题,假设我们将上面的程序进行一定的修改

struct Person {
    int m_age;
    void run() {
        cout << "Person :: run() " << m_age << endl;
    }
};

int main() {

    Person person1;
    person1.m_age = 10;
    person1.run();

    Person person2;
    person2.m_age = 20;
    person2.run();
    getchar();
    return 0;
}

两个Person类创建的对象,分别有属于自己的m_age成员变量,也分别调用了对应的run函数。在前面说过,person1调用的run函数与person2调用的run函数,是同一个run函数,即person1.run()与person2.run()执行的代码是同一份,对应Person类对象调用run函数后,得到的结果却不一样,其中person1.run()的结果为Person :: run() 10,person2.run()的结果为Person :: run() 20。而且前面也说过,对象的成员变量是保存在对象创建的内存区域,函数并不保存在对象中,所以,成员变量和函数是独立开的,对象的内存在栈空间,而函数的内存在代码区,那最终打印m_age的时候,是怎么知道该打印10还是20呢,这样的结果到底是怎么导致的呢?

可以这样来假设,假设当前的run函数是如下方式进行定义的,在调用函数时,可以传入一个参数,最终在函数内部,就可以获取到当前对象的成员变量了

void run(Person *person) {
    cout << "Person :: run() " << person->m_age << endl;
}

后面调用的时候,就可以通过这种方式来进行调用

struct Person {
    int m_age;
    void run(Person *person) {
        cout << "Person :: run() " << person->m_age << endl;
    }
};

int main() {

    Person person1;
    person1.m_age = 10;
    person1.run(&person1);

    Person person2;
    person2.m_age = 20;
    person2.run(&person2);
    getchar();
    return 0;
}

这样就可以办到,通过函数参数中的指针,间接的访问到该对象的存储空间,然后访问对象的成员变量了。所以,这是一种在函数里面可以访问到对象成员变量的一种方式。但是,如果每次声明函数时,都需要定义一个这样的参数,在调用函数时,都需要传入这个参数的话,就太麻烦了。好在编译器已经把这一切都做了,编译器在每个函数里面,都提供了一个this指针,并且该指针指向当前调用该函数的对象。所以可以这样理解,某个对象调用函数时,都会偷偷的将该对象赋值给this指针。即

struct Person {
    int m_age;
    void run() {
        //this = &person1; 假想的
        cout << "Person :: run() " << m_age << endl;
    }
};

int main() {

    Person person1;
    person1.m_age = 10;
    person1.run();
    getchar();
    return 0;
}

可以理解为函数中默认有一个隐式参数this指针,并且该隐式参数this指针存储着函数调用者的地址,所以上面的代码也可以这样写

struct Person {
    int m_age;
    void run() {
        cout << "Person :: run() " << this->m_age << endl;
    }
};

int main() {

    Person person1;
    person1.m_age = 10;
    person1.run();
    getchar();
    return 0;
}

现在就来看看具体是怎么实现的。首先假设有一个全局的函数func,然后再main函数中调用该func函数,对比与调用run函数的区别

void func() {

}

int main() {
    func();
    Person person1;
    person1.m_age = 10;
    person1.run();

    Person person2;
    person2.m_age = 20;
    person2.run();
    getchar();
    return 0;
}

相信你一定能想象,现在func函数生成的汇编是什么样的。对的!没错call [函数地址],并且来看一下func函数与run函数的区别

从run函数调用生成汇编代码中看到,在执行call指令之前,执行了lea操作。lea指令的作用是取出person1的内存地址,然后将内存地址,赋值给了ecx寄存器。将地址存到ecx寄存器以后,后面是怎么获取的呢?可以通过单步调试进入到call对应的函数中。

在这一段代码中,我们看到有一句mov指令 mov dword ptr [this],ecx,这句指令是将ecx里面存储的数据,放到[this]指针的存储空间,这样,this指针就成功的指向了person1。这样就可以在函数里面,通过this指针访问person1里面的成员变量了。

现在将整个流程梳理一遍:

  1. 在调用run函数之前,先将person1对象的地址,存放到了ecx寄存器
  2. 调用run函数
  3. 在函数内部,将ecx寄存器中存储的person1的地址值,存放到this指针对应的存储空间
  4. 通过this指针,访问person成员变量

但是你可能会这样想,在平时编码的过程中,并没有直接通过this指针来访问函数的成员变量,而是直接访问的。这个问题其实是语法糖,在表面上看,没有用到this,但是实际上用到了this,只不过编译器处理以后,我们可以省略,所以下面的两种写法是等价的。

void run() {
    cout << "Person :: run() " << this->m_age << endl;
}
void run() {
    cout << "Person :: run() " << m_age << endl;
}

究竟是不是等价的,看以下汇编就知道了。所以将这段代码转为汇编

struct Person {
    int m_age;
    void run() {
        cout << "Person :: run() " << m_age << endl;
    }
};

void func() {

}

int main() {
    func();
    Person person1;
    person1.m_age = 10;
    person1.run();

    Person person2;
    person2.m_age = 20;
    person2.run();
    getchar();
    return 0;
}

生成汇编

run函数生成的汇编

最终发现,两段代码,生成的汇编代码是完全一样的。

好的。现在已经了解到this指针的本质,那么问题来了,可以利用this.m_age来访问成员变量吗?

答:不可以,因为this是指针,必须用this->m_age来访问。

指针访问对象成员的本质

🤔下面这段代码,最后打印出来的每个成员变量的值是多少?

struct Person {
    int m_id;
    int m_age;
    int m_height;
    void display() {
        cout << "id =  " << m_id
            << ", age = " << m_age 
            << ", height = " << m_height << endl;
    }
};

int main() {
    Person person;
    person.m_id = 10;
    person.m_age = 20;
    person.m_height = 30;
    person.display();
    getchar();
    return 0;
}

话不多说,直接看以下生成的汇编代码。

首先,为成员变量赋值的汇编非常简单[下图],直接将立即数存放到成员变量对应的存储空间。

为了回答上面的问题,还需要再来看看指针变量是如何访问成员变量的

struct Person {
    int m_id;
    int m_age;
    int m_height;
    void display() {
        cout << "id =  " << m_id
            << ", age = " << m_age 
            << ", height = " << m_height << endl;
    }
};

int main() {
    Person person;
    person.m_id = 10;
    person.m_age = 20;
    person.m_height = 30;
    person.display();

    Person* p = &person;
    p->m_id = 10;
    p->m_age = 20;
    p->m_height = 30;
    getchar();
    return 0;
}

对应的汇编代码

补充:上图中eax中存储的即为对象person的地址值,然后根据对象的地址 + 成员变量的偏移,就能找到对应的成员变量。

如何利用指针间接访问所指向对象的成员变量原理总结:

  1. 从指针中取出对象的地址
  2. 利用对象的地址 + 成员变量的偏移量,计算出成员变量的地址
  3. 根据成员变量的地址访问成员变量的存储空间

说到这里,我想已经知道上面display函数的本质了吧?

前面讲到,以下两种写法是等价的

void display() {
    cout << "id =  " << m_id
        << ", age = " << m_age 
        << ", height = " << m_height << endl;
}
void display() {
    cout << "id =  " << this->m_id
        << ", age = " << this->m_age 
        << ", height = " << this->m_height << endl;
}

所以,答案我想已经有了吧!

现在已经知道,指针访问成员变量是通过偏移来访问的,那下面的这个打印结果又是多少呢?

struct Person {
    int m_id;
    int m_age;
    int m_height;
    void display() {
        cout << "id =  " << m_id
            << ", age = " << m_age 
            << ", height = " << m_height << endl;
    }
};

int main() {
    Person person;
    person.m_id = 10;
    person.m_age = 20;
    person.m_height = 30;
    Person* p = (Person*)&person.m_age;
    p->m_id = 40;
    p->m_age = 50;
    person.display();
    getchar();
    return 0;
}

打印的结果是10,40,50。

如果将上面代码中的person.display()换为p->display()得到的结果,又是什么呢?

demo下载地址

文章完。

相关文章

  • 08-this的本质

    this 现在有以下一段代码程序 这段程序,我们都很清楚,首先我们创建了一个person对象,然后为对象的m_ag...

  • 08-this关键字

  • 本质的本质

    这个世界为什么会有友谊这种东西?进化心理学认为这是因为在采集-狩猎时期,个人的食物供应充斥着不确定性,如果一个人长...

  • 本质的本质是什么

    越本质的东西越简单。世界上所有学问的最伟大共同特征是什么?简单!简单!还是简单!越本质的东西越简单。 如果...

  • 关于本质的本质思考

    《教父》里面有句话说:"花半秒就能看透事物本质的人,和花一辈子都看不清事物本质的人,注定是截然不同的命运”那么,为...

  • 《金融的本质》---信任的本质

    第一部分 一般说起金融危机,大家想到的都是什暴跌啊、破产倒闭之类的。但是股市、债市大跌就是金融危机么?这可不一定。...

  • 战略的本质--《经营的本质》

    一个企业能够走多远,取决于这个企业是否具有战略的思维和能力。从本质来说,战略就是选择做什么和不做什么。 企业的存在...

  • 人生的本质,社会的本质

    【书子颜】/文/与洛 关键词:世界的本质、认识世界,认识自我 什么是有,什么是无,什么是黑,什么是白?我们在不断的...

  • 本质

    白色是本质, 五谷杂粮是本质, 清净的是本质, 肮脏的是本质, 痛苦是本质, 恶也是本质, 棉麻的是本质, 不舒服...

  • 本质

    宗教的本质是回归,科学的本质是革新,艺术的本质是释放,经济的本质是交换,教育的本质是规范,哲学的本质是本质。 郑冠军/文

网友评论

      本文标题:08-this的本质

      本文链接:https://www.haomeiwen.com/subject/gsjnbctx.html