C++ 虛函數表解析

首先聲明,本文的大部份內容來自大牛文章 http://blog.csdn.net/haoel/article/details/1948051 而後加上本身的一些理解和實驗。ios

系統和編譯器: ubuntu 14.04 64bits + g++4.8.2c++

虛函數表

 

C++ 瞭解的人都應該知道虛函數(Virtual Function)是經過一張虛函數表(Virtual Table)來實現的。簡稱爲V-Table。在這個表中,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,因此,當咱們用父類的指針來操做一個子類的時候,這張虛函數表就顯得由爲重要了,它就像一個地圖同樣,指明瞭實際所應該調用的函數。ubuntu

 

這裏咱們着重看一下這張虛函數表。C++的編譯器應該是保證虛函數表的指針存在於對象實例中最前面的位置(這是爲了保證取到虛函數表的有最高的性能——若是有多層繼承或是多重繼承的狀況下)。 這意味着咱們經過對象實例的地址獲得這張虛函數表,而後就能夠遍歷其中函數指針,並調用相應的函數。函數

 

聽我扯了那麼多,我能夠感受出來你如今可能比之前更加暈頭轉向了。 不要緊,下面就是實際的例子,相信聰明的你一看就明白了。工具

 

假設咱們有這樣的一個類:性能

 

class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

 

按照上面的說法,咱們能夠經過Base的實例來獲得虛函數表。 下面是實際例程:測試

           typedef void(*Fun)(void);

            Base b;


            Fun pFun = NULL;

 

            cout << "虛函數表地址:" << (int*)(&b) << endl;

            cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

實際運行經果以下:(Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)spa

虛函數表地址:0012FED4.net

虛函數表 — 第一個函數地址:0044F148設計

Base::f

 

經過這個示例,咱們能夠看到,咱們能夠經過強行把&b轉成int *,取得虛函數表的地址,而後,再次取址就能夠獲得第一個虛函數的地址了,也就是Base::f(),這在上面的程序中獲得了驗證(把int* 強制轉成了函數指針)。經過這個示例,咱們就能夠知道若是要調用Base::g()Base::h(),其代碼以下:

 

            (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

 

這個時候你應該懂了吧。什麼?仍是有點暈。也是,這樣的代碼看着太亂了。沒問題,讓我畫個圖解釋一下。以下所示:

 

注意:在上面這個圖中,我在虛函數表的最後多加了一個結點,這是虛函數表的結束結點,就像字符串的結束符「/0」同樣,其標誌了虛函數表的結束。這個結束標誌的值在不一樣的編譯器下是不一樣的。在WinXP+VS2003下,這個值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個值是若是1,表示還有下一個虛函數表,若是值是0,表示是最後一個虛函數表。

這種狀況是考慮到多重繼承的時候考慮的,參考

 

 

 

  

下面,我將分別說明「無覆蓋」和「有覆蓋」時的虛函數表的樣子。沒有覆蓋父類的虛函數是毫無心義的。我之因此要講述沒有覆蓋的狀況,主要目的是爲了給一個對比。在比較之下,咱們能夠更加清楚地知道其內部的具體實現。

 

通常繼承(無虛函數覆蓋)

 

下面,再讓咱們來看看繼承時的虛函數表是什麼樣的。假設有以下所示的一個繼承關係:

 

請注意,在這個繼承關係中,子類沒有重載任何父類的函數。那麼,在派生類的實例中,其虛函數表以下所示:

 對於實例:Derive d; 的虛函數表以下:

  

咱們能夠看到下面幾點:

1)虛函數按照其聲明順序放於表中。

2)父類的虛函數在子類的虛函數前面。

 

 

通常繼承(有虛函數覆蓋)

 

覆蓋父類的虛函數是很顯然的事情,否則,虛函數就變得毫無心義。下面,咱們來看一下,若是子類中有虛函數重載了父類的虛函數,會是一個什麼樣子?假設,咱們有下面這樣的一個繼承關係。

 

爲了讓你們看到被繼承事後的效果,在這個類的設計中,我只覆蓋了父類的一個函數:f()。那麼,對於派生類的實例,其虛函數表會是下面的一個樣子:

 

咱們從表中能夠看到下面幾點,

1)覆蓋的f()函數被放到了虛表中原來父類虛函數的位置。

2)沒有被覆蓋的函數依舊。

 

這樣,咱們就能夠看到對於下面這樣的程序,

 

            Base *b = new Derive();

 

            b->f();

 

b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代,因而在實際調用發生時,是Derive::f()被調用了。這就實現了多態。

 

多重繼承(無虛函數覆蓋)

 

下面,再讓咱們來看看多重繼承中的狀況,假設有下面這樣一個類的繼承關係。注意:子類並無覆蓋父類的函數。

  

對於子類實例中的虛函數表,是下面這個樣子:

 

 

 咱們能夠看到:

1)  每一個父類都有本身的虛表。

2)  子類的成員函數被放到了第一個父類的表中。(所謂的第一個父類是按照聲明順序來判斷的)

 這樣作就是爲了解決不一樣的父類類型的指針指向同一個子類實例,而可以調用到實際的函數。

  

多重繼承(有虛函數覆蓋)

 

下面咱們再來看看,若是發生虛函數覆蓋的狀況。

 

下圖中,咱們在子類中覆蓋了父類的f()函數。

  

 

 

 下面是對於子類實例中的虛函數表的圖:

 

  

咱們能夠看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,咱們就能夠任一靜態類型的父類來指向子類,並調用子類的f()了。如:

 

            Derive d;

            Base1 *b1 = &d;

            Base2 *b2 = &d;

            Base3 *b3 = &d;

            b1->f(); //Derive::f()

            b2->f(); //Derive::f()

            b3->f(); //Derive::f()

 

            b1->g(); //Base1::g()

            b2->g(); //Base2::g()

            b3->g(); //Base3::g()

 

 

以上內容基本爲摘抄

下面的這個例子也爲原文的例子,因爲個人系統是64位的,因此須要將原來的int(32bits)換成long 64bits。另外,介紹下個人調試、測試工具:

1 加入了dump_mem函數,來打印相關的內存輸出,從而使內存的內容更加明瞭。

2 使用 nm a.out | c++filt 來查看可執行文件中的全部符號,從而和對應的函數調用關係對應

#include <iostream>
using namespace std;
#include <stdio.h>
#include <stdlib.h>


void dump_mem(void* pbeg, size_t size)
{
    char* pbyte = reinterpret_cast<char*>(pbeg);

    printf("dump bytes begin: 0x%08x, bytes size: %un\n", reinterpret_cast<unsigned long int>(pbeg), size);
    if (size == 0)
        return;

    puts("offset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
    puts("-----------------------------------------------------------");

    int col = 0;
    for (size_t i = 0; true; i++)
    {
        if (col == 0)
            printf("0x%08X: ", reinterpret_cast<unsigned long>(pbyte + i));
        printf("%02X", pbyte[i]& 0xff);

        if (i == size - 1)
            break;

        if (col == 15)
        {
            putchar('\n');
            col = 0;
        }
        else
        {
            putchar(' ');
            col++;
        }
    }

    putchar('\n');
}

class Base1 {
    public:
        virtual void f() { cout << "Base1::f" << endl; }
        virtual void g() { cout << "Base1::g" << endl; }
        virtual void h() { cout << "Base1::h" << endl; }

};

class Base2 {
    public:
        virtual void f() { cout << "Base2::f" << endl; }
        virtual void g() { cout << "Base2::g" << endl; }
        virtual void h() { cout << "Base2::h" << endl; }
};

class Base3 {
    public:
        virtual void f() { cout << "Base3::f" << endl; }
        virtual void g() { cout << "Base3::g" << endl; }
        virtual void h() { cout << "Base3::h" << endl; }
};


class Derive : public Base1, public Base2, public Base3 {
    public:
        virtual void f() { cout << "Derive::f" << endl; }
        virtual void g1() { cout << "Derive::g1" << endl; }
};


typedef void(*Fun)(void);

int main()
{
    Fun pFun = NULL;

    Base1 b1;
    Base2 b2;
    Base3 b3;

    Derive d;
    long ** pVtab = (long **)&d;

    cout <<"pVtable address:\t" <<hex<<(long) pVtab <<endl;
    cout <<"pVtable content:\t" <<hex <<(long) *pVtab <<endl;
    cout <<"pVtable+1 content:\t" <<hex <<(long) *(pVtab+1) <<endl;
    cout <<"pVtable+2 content:\t" <<hex <<(long) *(pVtab+2) <<endl;
    cout <<"pVtable[0] content:\t" <<hex <<(long) pVtab[0] <<endl;
    cout <<"pVtable[1] content:\t" <<hex <<(long) pVtab[1] <<endl;
    cout <<"pVtable[2] content:\t" <<hex <<(long) pVtab[2] <<endl;
    cout <<"pVtable[0][0] content:\t" <<hex << (long) pVtab[0][0]<<endl;
    cout <<"pVtable[0][1] content:\t" <<hex << (long) pVtab[0][1]<<endl;
    cout <<"pVtable[0][2] content:\t" <<hex << (long) pVtab[0][2]<<endl;
    cout <<"pVtable[1][0] content:\t" <<hex << (long) pVtab[1][0]<<endl;
    cout <<"pVtable[1][1] content:\t" <<hex << (long) pVtab[1][1]<<endl;
    cout <<"pVtable[1][2] content:\t" <<hex << (long) pVtab[1][2]<<endl;
    cout <<"pVtable[2][0] content:\t" <<hex << (long) pVtab[2][0]<<endl;
    cout <<"pVtable[2][1] content:\t" <<hex << (long) pVtab[2][1]<<endl;
    cout <<"pVtable[2][2] content:\t" <<hex << (long) pVtab[2][2]<<endl;

    //Base1's vtable
    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
    pFun = (Fun)pVtab[0][0];
    pFun();
    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
    pFun = (Fun)pVtab[0][1];
    pFun();

    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
    pFun = (Fun)pVtab[0][2];
    pFun();

    //Derive's vtable
    //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
    pFun = (Fun)pVtab[0][3];
    pFun();

    //The tail of the vtable
    pFun = (Fun)pVtab[0][4];
    cout<<pFun<<endl;


    //Base2's vtable
    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
    pFun = (Fun)pVtab[1][0];
    pFun();

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
    pFun = (Fun)pVtab[1][1];
    pFun();

    pFun = (Fun)pVtab[1][2];
    pFun();

    //The tail of the vtable
    pFun = (Fun)pVtab[1][3];
    cout<<pFun<<endl;



    //Base3's vtable
    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
    pFun = (Fun)pVtab[2][0];
    pFun();

    //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
    pFun = (Fun)pVtab[2][1];
    pFun();

    pFun = (Fun)pVtab[2][2];
    pFun();

    //The tail of the vtable
    pFun = (Fun)pVtab[2][3];
    cout<<pFun<<endl;

    dump_mem((void*)pVtab[0], 100);
    dump_mem((void*)pVtab[1], 100);
    dump_mem((void*)pVtab[2], 100);

 pFun = (Fun)pVtab[0][0];
    pFun();
    pFun = (Fun)pVtab[1][0];
    pFun();
    pFun = (Fun)pVtab[2][0];
    pFun();
    //pFun = (Fun)0x401604;
    //pFun();
    //pFun = (Fun)0X401556;
    //pFun();

    return 0;
}

 

編譯運行 得到輸出:

pVtable address:        7fffeafe67d0
pVtable content:        4019b0
pVtable+1 content:      4019e0
pVtable+2 content:      401a08
pVtable[0] content:     4019b0
pVtable[1] content:     4019e0
pVtable[2] content:     401a08
pVtable[0][0] content:  401574
pVtable[0][1] content:  401424
pVtable[0][2] content:  40144e
pVtable[1][0] content:  40159e
pVtable[1][1] content:  4014a2
pVtable[1][2] content:  4014cc
pVtable[2][0] content:  4015a4
pVtable[2][1] content:  401520
pVtable[2][2] content:  40154a
Derive::f
Base1::g
Base1::h
Derive::g1
1
Derive::f
Base2::g
Base2::h
1
Derive::f
Base3::g
Base3::h
0
dump bytes begin: 0x004019b0, bytes size: 100n
offset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
-----------------------------------------------------------
0x004019B0: 74 15 40 00 00 00 00 00 24 14 40 00 00 00 00 00
0x004019C0: 4E 14 40 00 00 00 00 00 AA 15 40 00 00 00 00 00
0x004019D0: F8 FF FF FF FF FF FF FF E0 1A 40 00 00 00 00 00
0x004019E0: 9E 15 40 00 00 00 00 00 A2 14 40 00 00 00 00 00
0x004019F0: CC 14 40 00 00 00 00 00 F0 FF FF FF FF FF FF FF
0x00401A00: E0 1A 40 00 00 00 00 00 A4 15 40 00 00 00 00 00
0x00401A10: 20 15 40 00
dump bytes begin: 0x004019e0, bytes size: 100n
offset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
-----------------------------------------------------------
0x004019E0: 9E 15 40 00 00 00 00 00 A2 14 40 00 00 00 00 00
0x004019F0: CC 14 40 00 00 00 00 00 F0 FF FF FF FF FF FF FF
0x00401A00: E0 1A 40 00 00 00 00 00 A4 15 40 00 00 00 00 00
0x00401A10: 20 15 40 00 00 00 00 00 4A 15 40 00 00 00 00 00
0x00401A20: 00 00 00 00 00 00 00 00 30 1B 40 00 00 00 00 00
0x00401A30: F6 14 40 00 00 00 00 00 20 15 40 00 00 00 00 00
0x00401A40: 4A 15 40 00
dump bytes begin: 0x00401a08, bytes size: 100n
offset      00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
-----------------------------------------------------------
0x00401A08: A4 15 40 00 00 00 00 00 20 15 40 00 00 00 00 00
0x00401A18: 4A 15 40 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00401A28: 30 1B 40 00 00 00 00 00 F6 14 40 00 00 00 00 00
0x00401A38: 20 15 40 00 00 00 00 00 4A 15 40 00 00 00 00 00
0x00401A48: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00401A58: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00401A68: 50 1B 40 00
Derive::f
Derive::f
Derive::f

 

使用 nm dump 全部符號表

ego@ubuntu:~/myProg/cpp$ nm a.out | c++filt 
0000000000602e18 d _DYNAMIC
0000000000603000 d _GLOBAL_OFFSET_TABLE_
00000000004012ce t _GLOBAL__sub_I__Z8dump_memPvm
0000000000401700 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 w _Jv_RegisterClasses
0000000000401291 t __static_initialization_and_destruction_0(int, int)
0000000000400afd T dump_mem(void*, unsigned long)
00000000004013fa W Base1::f()
0000000000401424 W Base1::g()
000000000040144e W Base1::h()
00000000004015d4 W Base1::Base1()
00000000004015d4 W Base1::Base1()
0000000000401478 W Base2::f()
00000000004014a2 W Base2::g()
00000000004014cc W Base2::h()
00000000004015ea W Base2::Base2()
00000000004015ea W Base2::Base2()
00000000004014f6 W Base3::f()
0000000000401520 W Base3::g()
000000000040154a W Base3::h()
0000000000401600 W Base3::Base3()
0000000000401600 W Base3::Base3()
0000000000401574 W Derive::f()
00000000004015aa W Derive::g1()
0000000000401616 W Derive::Derive()
0000000000401616 W Derive::Derive()
                 U std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >::operator<<(std::ios_base& (*)(std::ios_base&))@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >::operator<<(bool)@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >::operator<<(long)@@GLIBCXX_3.4
                 U std::ios_base::Init::Init()@@GLIBCXX_3.4
                 U std::ios_base::Init::~Init()@@GLIBCXX_3.4
0000000000401374 W std::ios_base::setf(std::_Ios_Fmtflags, std::_Ios_Fmtflags)
00000000004013d2 W std::hex(std::ios_base&)
0000000000603100 B std::cout@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@@GLIBCXX_3.4
0000000000603279 b std::__ioinit
0000000000401346 W std::operator&=(std::_Ios_Fmtflags&, std::_Ios_Fmtflags)
00000000004012e3 W std::operator&(std::_Ios_Fmtflags, std::_Ios_Fmtflags)
000000000040130b W std::operator~(std::_Ios_Fmtflags)
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@@GLIBCXX_3.4
0000000000401319 W std::operator|=(std::_Ios_Fmtflags&, std::_Ios_Fmtflags)
00000000004012f7 W std::operator|(std::_Ios_Fmtflags, std::_Ios_Fmtflags)
0000000000401b70 V typeinfo for Base1
0000000000401b50 V typeinfo for Base2
0000000000401b30 V typeinfo for Base3
0000000000401ae0 V typeinfo for Derive
0000000000401b60 V typeinfo name for Base1
0000000000401b40 V typeinfo name for Base2
0000000000401b28 V typeinfo name for Base3
0000000000401ac8 V typeinfo name for Derive
0000000000401aa0 V vtable for Base1
0000000000401a60 V vtable for Base2
0000000000401a20 V vtable for Base3
00000000004019a0 V vtable for Derive
00000000006030a0 V vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
0000000000603220 V vtable for __cxxabiv1::__vmi_class_type_info@@CXXABI_1.3
00000000004015a4 W non-virtual thunk to Derive::f()
000000000040159e W non-virtual thunk to Derive::f()
00000000004020d8 r __FRAME_END__
0000000000602e10 d __JCR_END__
0000000000602e10 d __JCR_LIST__
0000000000603098 D __TMC_END__
0000000000603098 B __bss_start
                 U __cxa_atexit@@GLIBC_2.2.5
0000000000603088 D __data_start
0000000000400ab0 t __do_global_dtors_aux
0000000000602e08 t __do_global_dtors_aux_fini_array_entry
0000000000603090 D __dso_handle
0000000000602df8 t __frame_dummy_init_array_entry
                 w __gmon_start__
0000000000602e08 t __init_array_end
0000000000602df8 t __init_array_start
00000000004016f0 T __libc_csu_fini
0000000000401680 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000603098 D _edata
0000000000603280 B _end
00000000004016f4 T _fini
00000000004008f8 T _init
0000000000400a10 T _start
0000000000603278 b completed.6972
0000000000603088 W data_start
0000000000400a40 t deregister_tm_clones
0000000000400ad0 t frame_dummy
0000000000400bf4 T main
                 U printf@@GLIBC_2.2.5
                 U putchar@@GLIBC_2.2.5
                 U puts@@GLIBC_2.2.5
0000000000400a70 t register_tm_clones

另外,注意,在上面的例子中,

  pFun = (Fun)pVtab[0][0];
    pFun();
    pFun = (Fun)pVtab[1][0];
    pFun();
    pFun = (Fun)pVtab[2][0]; pFun();

pVtable[0][0] content:  401574 pVtable[0][1] content: 401424 pVtable[0][2] content: 40144e pVtable[1][0] content: 40159e pVtable[1][1] content: 4014a2 pVtable[1][2] content: 4014cc pVtable[2][0] content: 4015a4 pVtable[2][1] content: 401520 pVtable[2][2] content: 40154a
pVtable[0][0]和pVtable[1][0]和pVtable[2][0]的值不相同

卻都調用了 Derive::f,這說明內部調用時有調整。

 

 

另外,記錄下有關 long ** pVtab = (long **)&d; 的使用。

 

pVtab 是一個指向指針的指針,  

p[0] 等同於 *p, 仍是一個指針,指向第一個虛函數表

p[1] 等同於 *(p+1), 仍是一個指針,指向第二個虛函數表

p[2] 等同於 *(p+2), 仍是一個指針,指向第三個虛函數表

p[0][0] 是一個long int,咱們這裏,是**p

p[1][2] 是一個long int,咱們這裏,是*(*(p_1) +2),就是base2::g()函數的地址, 而後把他付給函數指針,便可調用函數。

相關文章
相關標籤/搜索