c++ 虚函数内存浅析(一)

来源:互联网 发布:windows应用商店误删 编辑:程序博客网 时间:2024/05/17 03:33

c++ 虚函数内存浅析(一)


一:引子

   c++和其他面向对象的语言一样,都有多态,封装,继承的特性,多态主要靠重载和覆盖来实现。   如果用我自己的理解来对多态进行定义,那么“根据不同的上下文语境,使用不同的同名函数   需求的一种方法。”这样的说法可能更容易理解一些。虚函数是实现重载和覆盖的重要手段,   那么对于虚函数,编译器做了哪些工作;以及代码在cpu上到底是如何实现的呢?

二:正文

简单模式下的内存情景

  • 构造,析构函数顺序
    • 测试代码
      类的继承关系如下:
class BaseTestA{public:    BaseTestA()    {        m_countA = 0xa0;        printf("BaseTestA in \n");    }    ~BaseTestA()    {        printf("BaseTestA out \n");    }    void PrintFun()    {        printf("BaseTestA \n");    }    virtual void virPrint()    {        printf("Base virtual print A \n");    }    int m_countA;};// 单线继承class TestB : public BaseTestA{public:    TestB()    {        m_countB = 0xb0;        printf("TestB in \n");    }    ~TestB()    {        printf("TestB out \n");    }    virtual void PrintFun()    {        printf("TestB \n");    }    virtual void virPrint()    {        printf("virPrint print B \n");    }    virtual void virRun()    {        printf("virRun print B \n");    }    int m_countB;};

执行测试代码:

void test(){    BaseTestA *testA = new TestB;    testA->PrintFun();    testA->virPrint();    TestB testB;    testB.PrintFun();}int _tmain(int argc, _TCHAR* argv[]){    test();    // 等待结果查看    char c = getchar();    return 0;}

这里写图片描述

执行顺序: 父类的构造函数->子类的构造函数; 子类的析构函数->父类的析构函数。
简单模式下的顺序比较简单,有兴趣的可以用od或者windbg详细跟踪下一个子类的建立与消亡的过程。
在构造函数的跟踪过程中会发现:
1. 每一个类都会有一个虚函数表,对每一个类的构造时,都会将+0偏移的位置用虚函数表指针写入。
2. 子类的虚函数表和父类的虚函数表中,同名的函数在各自的虚函数表中的偏移是相同的。

虚函数在调用时,是this调用方式,一般将ecx作为this指针进行寄存器传参,而且虚函数在调用寻址时都是从虚函数表中的偏移获取所得如:
mov ecx,eax
call [eax+0x68]

  • 内存布局
    简单情形下的内存布局也比较简单,在这里发图说明:
    这里写图片描述

    成员变量和虚函数的顺序都是 父类在上,依次往下子类的。
    如果父类和子类有虚函数是覆盖或者重载实现的,那么子类的虚函数会覆盖掉父类的同名虚函数。
    这个是在编译器自动完成的,也就是说,如果一个exe生成了,那么每一个类的虚函数表都是固定值。

0 0