C++虛函數(shù);虛析構(gòu)函數(shù);類的存儲(chǔ)空間

字號(hào):

1、類的存儲(chǔ)空間
    在INTEL 32 CPU,VC6環(huán)境下,空類的一個(gè)實(shí)例占一個(gè)字節(jié);
    虛擬函數(shù)表指針占4個(gè)字節(jié)。
    2、虛函數(shù)的實(shí)現(xiàn)過(guò)程
    [網(wǎng)上很多講解, 本文有源代碼和部分匯編代碼]
    3、虛擬析構(gòu)函數(shù)
    無(wú)論基類的析構(gòu)函數(shù)是否為虛析構(gòu)函數(shù). 基類的析構(gòu)函數(shù)總是會(huì)被自動(dòng)調(diào)用的;
    但是, 如果用基類指針去操作一個(gè)了派生類對(duì)象,
    那么在delete這個(gè)基類指針時(shí),派生類的析構(gòu)函數(shù)將不會(huì)被調(diào)用.
    4. 補(bǔ)充: 一個(gè)C++類本身,在內(nèi)存里是有信息的, 除了上面 虛函數(shù)表, 還有靜態(tài)成員變量。
    VC6下的代碼:
    // Test.cpp : Defines the entry point for the console application.
    //
    #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
    #include
    class Base;
    class Derived;
    void GFunction(void);
    int main(int argc, char* argv[])
    {
    GFunction();
    char a = 127;
    a+=1;
    printf("new a = %d\n", a);
    getchar();
    return 0;
    }
    class Base
    {
    public:
    Base::Base()
    {
    };
    virtual Base::~Base()
    {
    printf("Base deconstruct\n");
    };
    virtual void Fun()
    {
    };
    int a ;
    };
    class Derived : public Base
    {
    public:
    Derived::Derived()
    {
    };
    virtual Derived::~Derived()
    {
    printf("Derived deconstruct\n");
    };
    virtual void Fun()
    {
    };
    };
    void GFunction(void)
    {
    printf("Class Base Sizeof =%d \n", sizeof(Base));
    printf("Class Derived Sizeof =%d \n\n", sizeof(Derived));
    Base* pA = (Base*)new Derived;
    pA->Fun(); // 虛函數(shù)調(diào)用
    delete pA;
    }
    pA->Fun()的匯編代碼如下:
    59: pA->Fun();
    0040D75C mov edx,dword ptr [ebp-10h] // edx為pA
    0040D75F mov eax,dword ptr [edx] // eax為pA對(duì)象的虛表指針pVTable
    0040D761 mov esi,esp
    0040D763 mov ecx,dword ptr [ebp-10h] // this指針存入ecx
    0040D766 call dword ptr [eax+4] // 函數(shù)地址:虛表指針+4, 就是虛表中第二項(xiàng)
    0040D769 cmp esi,esp
    0040D76B call __chkesp (00401b20)
    1. 如果成員函數(shù)不是虛函數(shù),那么編譯的時(shí)候,就直接指定了調(diào)用函數(shù)的入口;
    2. 如果是虛函數(shù),那么編譯時(shí)不直接指定函數(shù)入口,而是先在對(duì)象的內(nèi)存空間里取一個(gè)值(這個(gè)值就是虛函數(shù)表的地址,放在對(duì)象內(nèi)存空間的最前面4個(gè)字節(jié)里)。匯編代碼中會(huì)有取值的過(guò)程;
    3. 虛函數(shù)表中按順序存放著虛函數(shù)在地址空間中的地址:的第一個(gè)DWORD存儲(chǔ)的就是第一個(gè)虛函數(shù)的地址,第二個(gè)DWORD存儲(chǔ)的就是第二個(gè)虛函數(shù)的地址;
    3. 編譯器在編譯過(guò)程中已經(jīng)知道你調(diào)的那個(gè)函數(shù)在虛函數(shù)表中的序號(hào)。匯編代碼中會(huì)有體現(xiàn);
    4. 在運(yùn)行時(shí),就能正確找到調(diào)用函數(shù)的地址,并調(diào)用它.
    析構(gòu)函數(shù)的一點(diǎn)補(bǔ)充:
    在一個(gè)項(xiàng)目中,如果有N層派生類,編譯器總是保證所有基類的析構(gòu)函數(shù)都被依次調(diào)用,但問(wèn)題是,究竟從那層開(kāi)始調(diào)用呢?對(duì)于非虛析構(gòu)函數(shù),顯然是在編譯期間就直接確定的,對(duì)虛析構(gòu)函數(shù),在運(yùn)行時(shí),才能確定是從哪一層開(kāi)始往下層調(diào)用(基類)。
    事實(shí)上,和一般虛函數(shù)一樣,運(yùn)行時(shí),才確定要調(diào)用的析構(gòu)函數(shù),不過(guò)有些不同的是,析構(gòu)函數(shù)執(zhí)行完后,下一條指令就是基類析構(gòu)函數(shù)的CALL指令,一直到最上層為止。
    下面是一段debug下的反匯編,其中Base派生自BaseBase.可以看到~Base調(diào)用后,會(huì)自動(dòng)調(diào)用~BaseBase
    virtual Base::~Base() // 基類析構(gòu)函數(shù)
    {
    00401740 push ebp
    00401741 mov ebp,esp
    00401743 sub esp,0CCh
    00401749 push ebx
    0040174A push esi
    0040174B push edi
    0040174C push ecx
    0040174D lea edi,[ebp-0CCh]
    00401753 mov ecx,33h
    00401758 mov eax,0CCCCCCCCh
    0040175D rep stos dword ptr es:[edi]
    0040175F pop ecx
    00401760 mov dword ptr [ebp-8],ecx
    00401763 mov eax,dword ptr [this]
    00401766 mov dword ptr [eax],offset Base::`vftable' (44533Ch)
    printf("Base deconstruct\n");
    0040176C push offset string "Base deconstruct\n" (445344h)
    00401771 call printf (405760h)
    00401776 add esp,4
    };
    00401779 mov ecx,dword ptr [this]
    0040177C call BaseBase::~BaseBase (4017A0h) // 上層基類也緊跟著調(diào)用了