先看代碼:
#include <iostream> using namespace std; class Base { public: virtual void f() {cout<<"base::f"<<endl;} virtual void g() {cout<<"base::g"<<endl;} virtual void h() {cout<<"base::h"<<endl;} }; class Derive : public Base{ public: void g() {cout<<"derive::g"<<endl;} }; //可以稍后再看 int main () { cout<<"size of Base: "<<sizeof(Base)<<endl; typedef void(*Func)(void); Base b; Base *d = new Derive(); long* pvptr = (long*)d; long* vptr = (long*)*pvptr; Func f = (Func)vptr[0]; Func g = (Func)vptr[1]; Func h = (Func)vptr[2]; f(); g(); h(); return 0; }
都知道C++中的多態是用虛函數實現的: 子類覆蓋父類的虛函數, 然后聲明一個指向子類對象的父類指針, 如Base *b = new Derive();
當調用b->f()時, 調用的是子類的Derive::f()。
這種機制內部由虛函數表實現,下面對虛函數表結構進行分析,並且用GDB驗證。
1. 基礎知識:
(1) 32位os 指針長度為4字節, 64位os 指針長度為8字節, 下面的分析環境為64位 linux & g++ 4.8.4.
(2) new一個對象時, 只為類中成員變量分配空間, 對象之間共享成員函數。
2. _vptr
運行下上面的代碼發現sizeof(Base) = 8, 說明編譯器在類中自動添加了一個8字節的成員變量, 這個變量就是_vptr, 指向虛函數表的指針。
_vptr有些文章里說gcc是把它放在對象內存的末尾,VC是放在開始, 我編譯是用的g++,驗證了下是放在開始的:
驗證代碼:取對象a的地址與a第一個成員變量n的地址比較,如果不等,說明對象地址開始放的是_vptr. 也可以用gdb直接print a 會發現_vptr在開始
class A { public: int n; virtual void Foo(void){} }; int main() { A a; char *p1 = reinterpret_cast<char*>(&a); char *p2 = reinterpret_cast<char*>(&a.n); if(p1 == p2) { cout<<"vPtr is in the end of class instance!"<<endl; }else { cout<<"vPtr is in the head of class instance!"<<endl; } return 1; }
(3) 虛函數表
包含虛函數的類才會有虛函數表, 同屬於一個類的對象共享虛函數表, 但是有各自的_vptr.
虛函數表實質是一個指針數組,里面存的是虛函數的函數指針。
Base中虛函數表結構:
Derive中虛函數表結構:
(4)驗證
運行上面代碼結果:
size of Base: 8
base::f
derive::g
base::h
說明Derive的虛函數表結構跟上面分析的是一致的:
d對象的首地址就是vptr指針的地址-pvptr,
取pvptr的值就是vptr-虛函數表的地址
取vptr中[0][1][2]的值就是這三個函數的地址
通過函數地址就直接可以運行三個虛函數了。
函數表中Base::g()函數指針被Derive中的Derive::g()函數指針覆蓋, 所以執行的時候是調用的Derive::g()
(5)多繼承
附 GDB調試: (1) #生成帶有調試信息的可執行文件 g++ test.cpp -g -o test (2) #載入test gdb test (3) #列出Base類代碼 (gdb) list Base 1 #include <iostream> 2 3 using namespace std; 4 5 class Base { 6 public: 7 virtual void f() {cout<<"base::f"<<endl;} 8 virtual void g() {cout<<"base::g"<<endl;} 9 virtual void h() {cout<<"base::h"<<endl;} 10 }; (4) #查看Base函數地址 (gdb) info line 7 Line 7 of "test.cpp" starts at address 0x400ac8 <Base::f()> and ends at 0x400ad4 <Base::f()+12>. (gdb) info line 8 Line 8 of "test.cpp" starts at address 0x400af2 <Base::g()> and ends at 0x400afe <Base::g()+12>. (gdb) info line 9 Line 9 of "test.cpp" starts at address 0x400b1c <Base::h()> and ends at 0x400b28 <Base::h()+12>. (5)#列出Derive代碼 (gdb) list Derive 7 virtual void f() {cout<<"base::f"<<endl;} 8 virtual void g() {cout<<"base::g"<<endl;} 9 virtual void h() {cout<<"base::h"<<endl;} 10 }; 11 12 class Derive : public Base{ 13 public: 14 void g() {cout<<"derive::g"<<endl;} 15 }; (6)#查看Derive函數地址 (gdb) info line 14 Line 14 of "test.cpp" starts at address 0x400b46 <Derive::g()> and ends at 0x400b52 <Derive::g()+12>. (7)#start執行程序,n單步執行 (gdb) start Temporary breakpoint 1, main () at test.cpp:19 19 cout<<"size of Base: "<<sizeof(Base)<<endl; (gdb) n size of Base: 8 22 Base b; (gdb) 23 Base *d = new Derive(); (gdb) 25 long* pvptr = (long*)d; (gdb) 26 long* vptr = (long*)*pvptr; (gdb) 27 Func f = (Func)vptr[0]; (gdb) 28 Func g = (Func)vptr[1]; (gdb) 29 Func h = (Func)vptr[2]; (gdb) 31 f(); (gdb) (8) #print d對象, 0x400c90為成員變量_vptr的值,也就是函數表的地址 (gdb) p *d $4 = {_vptr.Base = 0x400c90 <vtable for Derive+16>} (gdb) p vptr $6 = (long *) 0x400c90 <vtable for Derive+16> (9) #查看函數表值,與之前查看函數地址一致 (gdb) p (long*)vptr[0] $9 = (long *) 0x400ac8 <Base::f()> (gdb) p (long*)vptr[1] $10 = (long *) 0x400b46 <Derive::g()> (gdb) p (long*)vptr[2] $11 = (long *) 0x400b1c <Base::h()>
另vptr. vtable內存位置, refer http://www.tuicool.com/articles/iUB3Ebi