成員函數在內存中的分佈

要看成員函數在內存中的分佈,那需要先獲得成員函數的地址,於是可以用函數指針指向成員函數,再打印地址,先來看下面一段代碼:

#include <iostream>
#include <stdio.h>

using namespace std;

class ClassA
{
public:
    ClassA(){m_data1 = 1; m_data2 = 2;}
    int m_data1;
    int m_data2;

    void func1(){cout << "ClassA::func1" << endl;}
    void func2(){cout << "ClassA::func2" << endl;}
    virtual void vfunc1(){cout << "ClassA::vfunc1" << endl;}
    virtual void vfunc2(){cout << "ClassA::vfunc2" << endl;}
};

class ClassB : public ClassA
{
public:
    ClassB(){m_data3 = 3;}
    int m_data3;
    void func2(){cout << "ClassB::func2" << endl;}
    virtual void vfunc1(){cout << "ClassB::vfunc1" << endl;}
};

class ClassC : public ClassB
{
public:
    ClassC(){m_data4= 5; m_data1 = 4;}
    int m_data1;
    int m_data4;
    void func2(){cout << "ClassC::func2" << endl;}
    virtual void vfunc1(){cout << "ClassC::vfunc1" << endl;}
};

int main()
{
    ClassA a;
    ClassB b;
    ClassC c;
	
	//函數返回值類型 (* 指針變量名) (函數參數列表);
    //返回類型(類名::*指針名)(參數)=&類名::函數名;
    void (ClassA::*pmfa)();

    pmfa = &ClassA::func1;
    printf("ClassA::func1=%#x  ", pmfa);
    (a.*pmfa)();

    pmfa = &ClassA::func2;
    printf("ClassA::func2=%#x  ", pmfa);
    (a.*pmfa)();

    pmfa = &ClassA::vfunc1;
    printf("ClassA::vfunc1=%#x ", pmfa);
    (a.*pmfa)();

    pmfa = &ClassA::vfunc2;
    printf("ClassA::vfunc2=%#x ", pmfa);
    (a.*pmfa)(); printf("\n");


    void (ClassB::*pmfb)();
    pmfb = &ClassB::func1;
    printf("ClassB::func1=%#x  ", pmfb);
    (b.*pmfb)();

    pmfb = &ClassB::func2;
    printf("ClassB::func2=%#x  ", pmfb);
    (b.*pmfb)();

    pmfb = &ClassB::vfunc1;
    printf("ClassB::vfunc1=%#x ", pmfb);
    (b.*pmfb)();

    pmfb = &ClassB::vfunc2;
    printf("ClassB::vfunc2=%#x ", pmfb);
    (b.*pmfb)();    printf("\n");


    void (ClassC::*pmfc)();
    pmfc = &ClassC::func1;
    printf("ClassC::func1=%#x  ", pmfc);
    (c.*pmfc)();

    pmfc = &ClassC::func2;
    printf("ClassC::func1=%#x  ", pmfc);
    (c.*pmfc)();

    pmfc = &ClassC::vfunc1;
    printf("ClassC::vfunc1=%#x ", pmfc);
    (c.*pmfc)();

    pmfc = &ClassC::vfunc2;
    printf("ClassC::vfunc2=%#x ", pmfc);
    (c.*pmfc)(); printf("\n");
    
    return 0;
}

運行結果:

ClassA::func1=0x565bf198  ClassA::func1
ClassA::func2=0x565bf1de  ClassA::func2		
ClassA::vfunc1=0x1 ClassA::vfunc1
ClassA::vfunc2=0x5 ClassA::vfunc2

ClassB::func1=0x565bf198  ClassA::func1
ClassB::func2=0x565bf2ec  ClassB::func2
ClassB::vfunc1=0x1 ClassB::vfunc1
ClassB::vfunc2=0x5 ClassA::vfunc2

ClassC::func1=0x565bf198  ClassA::func1
ClassC::func1=0x565bf3be  ClassC::func2
ClassC::vfunc1=0x1 ClassC::vfunc1
ClassC::vfunc2=0x5 ClassA::vfunc2

從運行結果可以看出:

  1. 對於類的普通函數(ClassA::func1),子類在繼承後並不會複製函數.
  2. 而當子類override父類的同名函數後,子類函數(ClassA::func2)有新的函數地址.
  3. vfunc1就有點奇怪了,子類明明override了,怎麼不同類的vfunc1函數地址相同呢? 如果函數地址相同,那多態是怎麼實現的,更奇怪的是從輸出結果來看,確實實現了多態調用?

我們知道對於含有虛函數的類對象,編譯器會爲類生成一個是虛函數表,如果vfunc1,vfunc2指向的是虛函數表,再通過查找虛函數表選出對應函數,好像還能解釋得通目前的現象(雖然(0x01, 0x05)這兩個地址看着就有點不像一個正常地址),於是我打印了出虛函數表的地址

#include <iostream>
#include <stdio.h>

using namespace std;

class ClassA
{
public:
    ClassA(){m_data1 = 1; m_data2 = 2;}
    int m_data1;
    int m_data2;

    void func1(){cout << "ClassA::func1" << endl;}
    void func2(){cout << "ClassA::func2" << endl;}
    virtual void vfunc1(){cout << "ClassA::vfunc1" << endl;}
    virtual void vfunc2(){cout << "ClassA::vfunc2" << endl;}
};

class ClassB : public ClassA
{
public:
    ClassB(){m_data3 = 3;}
    int m_data3;
    void func2(){cout << "ClassB::func2" << endl;}
    virtual void vfunc1(){cout << "ClassB::vfunc1" << endl;}
};

class ClassC : public ClassB
{
public:
    ClassC(){m_data4= 5; m_data1 = 4;}
    int m_data1;
    int m_data4;
    void func2(){cout << "ClassC::func2" << endl;}
    virtual void vfunc1(){cout << "ClassC::vfunc1" << endl;}
};

int main()
{
    ClassA a;
    ClassB b;
    ClassC c;

	long *v;    // v is the address of vptr
    long *vtbl0;    // vtbl0 is the address of vtbl[0]
    
	v = (long*)&a;
    vtbl0 = (long*)(*v);
    cout << "ClassA.vptr" << v << " ClassA.vtbl[0]" << vtbl0 << endl;

	v = (long*)&b;
    vtbl0 = (long*)(*v);
    cout << "ClassB.vptr" << v << " ClassB.vtbl[0]" << vtbl0 << endl;

    v = (long*)&c;
    vtbl0 = (long*)(*v);
    cout << "ClassC.vptr" << v << " ClassC.vtbl[0]" << vtbl0 << endl;

	return 0;    
}
    

運行結果:

ClassA.vptr=0xfffd6d78 ClassA.vtbl[0]=0x56650e88
ClassB.vptr=0xfffd6d84 ClassB.vtbl[0]=0x56650e78
ClassC.vptr=0xfffd6d94 ClassC.vtbl[0]=0x56650e68

向地址中可以看出不同類有自己的虛函數表,所以vfunc1,vfunc2顯然不是指向類的虛函數表

這裏涉及到從虛函數調用出發到查找虛函數表這個過程編譯器是如何實現的.就像一個黑盒子,我們知道進去的是什麼,也知道輸出是什麼,卻不知道中間的處理過程是什麼樣的.有知道的朋友,歡迎留言,我的郵箱地址([email protected])

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章