標籤:poi clu log func 子類 32位 lis art inux
先看代碼:
#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) #載入testgdb test(3) #列出Base類代碼(gdb) list Base1 #include <iostream>23 using namespace std;45 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 8Line 8 of "test.cpp" starts at address 0x400af2 <Base::g()> and ends at 0x400afe <Base::g()+12>.(gdb) info line 9Line 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 };1112 class Derive : public Base{13 public:14 void g() {cout<<"derive::g"<<endl;}15 };(6)#查看Derive函數地址(gdb) info line 14Line 14 of "test.cpp" starts at address 0x400b46 <Derive::g()> and ends at 0x400b52 <Derive::g()+12>.(7)#start執行程式,n逐步執行(gdb) startTemporary breakpoint 1, main () at test.cpp:1919 cout<<"size of Base: "<<sizeof(Base)<<endl;(gdb) nsize of Base: 822 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
C++虛函數表分析