C/C++教程

C++多态原理

本文主要是介绍C++多态原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
编译环境:win10;x64;debug
struct Base
{
    int base=0;
    virtual void Print() { };
    virtual void Test() { };
    virtual void Test1() {  };
    virtual void Test2() {  };
    virtual void Test3() {  };
    virtual void Test4() {  };
    virtual void Test5() {  };
    virtual void Test6() {  };
    virtual void Test7() {  };
    virtual void Test8() {  };
    virtual void Test9() {  };
};

struct  Son:Base
{
    int son=0;
    void Print() { };
    void Test() { };
    void Test1() { };
    void Test2() { };
    void Test3() { };
    void Test4() { };
    void Test5() { };
    void Test6() { };
    void Test7() { };
    void Test8() { };
    void Test9() { };
};

int main()
{
    Base* p = new Son();
    p->base = 10;
    p->Print();
    p->Test();
    p->Test1();
    p->Test2();
    p->Test3();
    p->Test4();
    p->Test5();
    p->Test6();
    p->Test7();
    p->Test8();
    p->Test9();
    cout << sizeof(p);
}

  多态:一个对象的不同表现形式。多态的实现关键在于父类的虚函数被子类重写,当使用父类指针去存储子类对象时会根据虚表调用重写的方法。在C++子类可以不使用关键字override,依然可以实现重写。

内存分布:

cout << sizeof(p);最后打印出的结果是24,son类自身有一个字段,继承了父类一个字段,那还有八个字节是什么?

小篮圈是该对象的首地址 ,目前我们不知道是什么,在new一个对象的时候会向堆申请sizeof(son)个字节的内存,后面的两个字段已经被归0,这就是son对象的内存分布。(new 对象的时候后面加括号,会帮我们把字段归0)

当我们给base字段赋值完成后,第一个字段就变成了0a 00..,这说明从父类继承发字段在自身字段之前。 

 当我们总结出内存分布后,我们去未知的内存里看一下哪里有什么?

 这是一段连续的内存,里面存了不知道的指向哪里的内存?这里我们先不管,继续执行。

执行虚方法:

p->Print();
//从p指针取八位,存入rax寄存器,这里取出了son对象的地址  [p]=new Son(); rax=son对象地址
00007FF6BA13251E  mov         rax,qword ptr [p]  
//从寄存器rax取八位存入rax   rax=son的首地址=对象的第一条未知地址
00007FF6BA132522  mov         rax,qword ptr [rax]  
//把son对象地址写入rcx寄存器
00007FF6BA132525  mov         rcx,qword ptr [p]  
//相当于调用了rax寄存器指向的地址 9f 11 13 ba f6 7f 00 00 ==0x7ff6ba13119f
00007FF6BA132529  call        qword ptr [rax]

我们继续跟进

这里已经告诉我们了 0x7ff6ba13119f 这条地址指向的是son的Print方法。    Son::Print (07FF6BA132050h)     到这里我们已经知道,son对象的第一条地址是son对象的地址。我们继续跟进

 

p->Print();
//从p指针取八位,存入rax寄存器,这里取出了son对象的地址  [p]=new Son(); rax=son对象地址
00007FF6BA13251E  mov         rax,qword ptr [p]  
//从寄存器rax取八位存入rax   rax=son的首地址=对象的第一条未知地址
00007FF6BA132522  mov         rax,qword ptr [rax]  
//把son对象地址写入rcx寄存器
00007FF6BA132525  mov         rcx,qword ptr [p]  
//相当于调用了rax寄存器指向的地址 9f 11 13 ba f6 7f 00 00 ==0x7ff6ba13119f
00007FF6BA132529  call        qword ptr [rax]
 

从我们上面的汇编代码可以看出,和在函数内部执行来看,rcx的地址就是son对象的地址,也就是说在执行函数的时候,把对象的地址也传入进来了。接下来看下一个虚方法的执行

69:     p->Test();
//流程同上
00007FF6BA13252B  mov         rax,qword ptr [p]  
00007FF6BA13252F  mov         rax,qword ptr [rax]  
00007FF6BA132532  mov         rcx,qword ptr [p]  
//这里地址偏移了八位   0x7ff6ba13150f
00007FF6BA132536  call        qword ptr [rax+8]

 

 

第二个地址和test方法的地址相同,然后我们去对比一下两个蓝色区域的地址,右边蓝色区域的地址等于左边蓝色区域执行跳转语句的地址。

到这里我们已经知道了,son对象的一个未知地址是son对象第一个方法跳转语句的地址,这个就是虚方法被重写后能够被正确调用的真相。我这里有11个虚函数,结果只占用八个字节,因为永远只会存首个重写方法的地址,右边蓝色区域的地址也被称为虚表。

虚表生成在什么时候?

 在对象刚创建出来的时候内存被cd填充。

当跳转到这一步的时候,内存会被0填充。

 

调用son对象构造函数的时候,虚表会被创建出来。son类在没有构造函数的时候,生成了默认构造,并调用了。(并不是所有没有构造函数的类都会生成并调用构造函数) 

这篇关于C++多态原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!