国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
C++虛函數(shù)表與對象布局(轉(zhuǎn))
每個含有虛函數(shù)的類有一張?zhí)摵瘮?shù)表(vtbl),表中每一項指向一個虛函數(shù)的地址,實現(xiàn)上是一個函數(shù)指針的數(shù)組。

  虛函數(shù)表既有繼承性又有多態(tài)性。每個派生類的vtbl繼承了它各個基類的vtbl,如果基類vtbl中包含某一項,則其派生類的vtbl中也將包含同樣的一項,但是兩項的值可能不同。如果派生類重載(override)了該項對應的虛函數(shù),則派生類vtbl的該項指向重載后的虛函數(shù),沒有重載的話,則沿用基類的值。

  在類對象的內(nèi)存布局中,首先是該類的vtbl指針,然后才是對象數(shù)據(jù)。在通過對象指針調(diào)用一個虛函數(shù)時,編譯器生成的代碼將先獲取對象類的vtbl指針,然后調(diào)用vtbl中對應的項。對于通過對象指針調(diào)用的情況,在編譯期間無法確定指針指向的是基類對象還是派生類對象,或者是哪個派生類的對象。但是在運行期間執(zhí)行到調(diào)用語句時,這一點已經(jīng)確定,編譯后的調(diào)用代碼能夠根據(jù)具體對象獲取正確的vtbl,調(diào)用正確的虛函數(shù),從而實現(xiàn)多態(tài)性。分析一下這里的思想所在,問題的實質(zhì)是這樣,對于發(fā)出虛函數(shù)調(diào)用的這個對象指針,在編譯期間缺乏更多的信息,而在運行期間具備足夠的信息,但那時已不再進行綁定了,怎么在二者之間作一個過渡呢?把綁定所需的信息用一種通用的數(shù)據(jù)結(jié)構記錄下來,該數(shù)據(jù)結(jié)構可以同對象指針相聯(lián)系,在編譯時只需要使用這個數(shù)據(jù)結(jié)構進行抽象的綁定,而在運行期間將會得到真正的綁定。這個數(shù)據(jù)結(jié)構就是vtbl。可以看到,實現(xiàn)用戶所需的抽象和多態(tài)需要進行后綁定,而編譯器又是通過抽象和多態(tài)而實現(xiàn)后綁定的。

  下面說一下多重繼承。多重繼承的兩個基類如果繼承了同一個類,則其派生類相當于繼承了該類兩次,vtbl也繼承了兩次。對象布局中,該類的數(shù)據(jù)有兩份,vtbl指針有兩個,分別指向兩次被繼承的vtbl。但派生類重載該類的虛函數(shù)時只能重載一次,那么重載后的函數(shù)地址將占據(jù)vtbl的哪個位置?通過寫程序測試,我覺得應該是同時出現(xiàn)在所繼承的兩個vtbl的相應位置,有待進一步驗證。

  說到虛函數(shù)機制,對象指針的類型轉(zhuǎn)換也是要弄清的,這里就不說了。還有一個this指針的問題,提一下。虛函數(shù)調(diào)用的時候也是需要傳遞this指針的,這沒什么奇怪,但是這時的this指針就隱含著一個問題,它要和實際調(diào)用的虛函數(shù)相一致,即this指針也要實現(xiàn)多態(tài)性。在多重繼承的情況下,這個問題不是那么簡單的,請參考[《C++語言的設計和演化》p203]。

  C++虛函數(shù)表深度分析

  昨天聽完彭老師的C++的講座,感覺很不錯,但之后留了一個疑問,就是關于虛函數(shù)表的機制,課下和彭老師的討論似乎也沒能完全解惑,我的疑問主要就是:

  1:虛函數(shù)表到底是怎么工作的,for類,還是for對象

  2:如果for類,那么基類和派生類是共用一表,還是各有各的表(物理上)

  3:如果共用一表的話,總是后面的覆蓋前面的函數(shù)地址,那不是很容易出現(xiàn)混亂嗎?

  帶著這三個疑問,趁著熱呼勁,我搜了搜關于虛函數(shù)表的DASM的文章,當然了,能搜到的幾篇都是for VC編譯器的

  初步得出了以前結(jié)論:

  1:虛表(虛函數(shù)表)是for類的

  2:基類和派生類是各有各的表,也就是說他們的物理地址是分開的,基類和派生類的虛表的唯一關聯(lián)是:當派生類沒有實現(xiàn)

  基類虛函數(shù)的重載時,派生類會直接把自己表的該函數(shù)地址值寫為基類的該函數(shù)地址值.

  3:任何一個有虛表的類,在實例化時不允許其虛表內(nèi)有項為空->純虛類不能初始化對象

  4:帶虛表的類在對象構造函數(shù)中,會把一個指針指向該類虛表地址,我在這給它起個名字叫vp;

  5:僅對于VC和BC兩種編譯器論,如果該類帶有虛表,那么該類的對象的首地址就是虛表地址,也是this指針指向虛表

  下面我就用IDE Borland C++ Builder 6.0 sp4,編譯器版本Borland C++ 5.5,來驗證一下:

  首先打開BCB6建立一個控制臺程序,寫上下面幾個備用類

  #include <conio.h>

  #include <stdio.h>

  #pragma hdrstop

  #pragma argsused

  class A

  {

  public:

  __stdcall A()

  {

  }

  virtual void __stdcall output()

  {

  printf("Class An");

  }

  virtual void __stdcall output2()

  {

  }

  };

  class B :public A

  {

  public:

  void __stdcall output()

  {

  printf("Class Bn");

  }

  };

  class C:public A

  {

  public :

  void __stdcall output()

  {

  printf("Class Cn");

  }

  };

  幾個類很簡單,B和C是A的派生

  下面先寫一個引子主程序,用來驗證虛表的存在:

  int main(int argc, char* argv[])

  {

  B b;

  printf("%d",sizeof(b));

  }

  結(jié)果是8

  我把A類的兩個virtual都去掉后再運行一次

  結(jié)果是4

  這說明了有virtual比沒virtual的對象多了32位,在win32中,32位正好是一個地址,那么這個地址就應該指向的是虛表

  看來虛表果然存在,那么虛表指針是在對象什么時候生成的呢?我改一下main函數(shù)

  int main(int argc, char* argv[])

  {

  A *pa;

  B b;

  C c;

  A a;

  pa=&b;

  pa->output();

  getch();

  return 0;

  }

  這應該是一個很經(jīng)典的教科書上講多態(tài)的例子,如果有virtual輸出Class B,如果沒virtual輸出Class A

  現(xiàn)在看一下這段代碼的反編譯代碼,我把BCB6的full debug模式打開,在 B b; 處設斷點

  圖片

  我們可以看到在b執(zhí)行完基類的構造函數(shù)后,執(zhí)行了

  mov edx,0x0040c114

  mov [ebp-0x0x],edx

  而這兩句話經(jīng)驗證,在沒有virtual關鍵字時是沒有的,讓我們記住0x0040c114這個地址先

  [ebp-0x0x]是this指針,我們目前猜測這段話就是把虛表的地址寫入this指針

  我們再看C c;后的反編譯代碼

  mov eax,0x0040c0f8

  mov [ebp-0x14],eax

  看來不同的類具有不同的虛表地址,也就是不同的類的表從物理上是不同的

  我們現(xiàn)在來探討虛表工作的原理

  我們對比一下pa->output()在有沒有virtual修飾時候的區(qū)別

  mov eax,[ebp-0x04]

  push eax

  mov edx,[eax]

  call dword ptr [edx]

  這是有virtual的

  push dword ptr [ebp-0x04]

  call A::output();

  這是沒有virtual的

  我們分析一下asm代碼,可以得出虛表的過程,先把根據(jù)this地址得到虛表地址,然后由虛表項里存放的函數(shù)指針地址,訪問

  相應的函數(shù),如果有多個虛函數(shù),且調(diào)用的是第N個虛函數(shù),那么上句call指令就會被更改為這樣的形式:call dword ptr

  [edx-4*(N-1)])

  一上是我們對dasm代碼做的一些推測,一會兒我們還要進一步驗證這些

  我們仔細看反編譯的結(jié)果,發(fā)現(xiàn)在A a;的dasm結(jié)果中,好象沒有vp初始化的一步,我查了其他文獻針對VC編譯器的dasm結(jié)

  果,發(fā)現(xiàn)VC編譯器的dasm結(jié)果里是有初始化vp的一步的,類似

  004010E8 mov dword ptr [eax],offset Derive::`vftable' (0042201c)

  我現(xiàn)在就得出這樣一個結(jié)論,在BC編譯器中很可能對于基類的對象構造函數(shù)作出了這樣的優(yōu)化,就是默認把this指針指向

  虛表地址,所以我們看不到這樣的dasm結(jié)果

  我還發(fā)現(xiàn),對于類的構造函數(shù)處理,VC和BC的編譯器也是不一樣的

  如果我們在類里面沒有寫構造函數(shù),VC會自動為我們加一個構造函數(shù),比如

  class Base {

  public:

  void __stdcall Output() {

  printf("Class Basen");

  }

  };

  我們得到這樣的dasm:

  004010D9 pop ecx

  004010DA mov dword ptr [ebp-4],ecx

  004010DD mov ecx,dword ptr [ebp-4]

  004010E0 call @ILT+30(Base::Base) (00401023)

  可以看到自動生成構造函數(shù)地址

  但在BC中,我們沒有看到這樣的代碼

  當我們把上面的A類里面的構造函數(shù)刪去后,這是得到的A a;的dasm

  mov edx, 0x0040c0f0

  mov [ebp-0x04],ecx

  完全找不到構造函數(shù)的影子,我猜測這也是編譯器對構造函數(shù)所作出的優(yōu)化

  我這里不評價兩種編譯器在這問題上的優(yōu)次,我繼續(xù)回到正題,驗證我們的結(jié)論的正確性

  因為按照我們的推測,0x0040c114就是虛表地址

  那么按照此理,我們通過訪問虛表地址的內(nèi)容里的第一個函數(shù)地址,就能訪問output函數(shù),而虛表的地址就是this地址,是這樣

  嗎,我再編了個main函數(shù)

  int main(int argc, char* argv[])

  {

  A *pa;

  B b;

  C c;

  A a;

  //pa=&b;

  //pa->output();

  //printf("%d",sizeof(b));

  typedef void (__stdcall *PF)(void);

  void *pthis=&b;

  PF pf=(PF)(*(unsigned int*)pthis);

  printf("%x",pf);

  printf("n");

  pf=(PF)(*(unsigned int*)pf);

  pf();

  getch();

  return 0;

  }

  先來解釋一下這段代碼

  typedef void (__stdcall *PF)(void);

  聲明了配搭output的函數(shù)指針

  void *pthis=&b;

  用來得到b的this地址,它是指向虛表地址的

  PF pf=(PF)(*(unsigned int*)pthis);

  用來得到this地址的內(nèi)容,也就是虛表地址

  然后我們把虛表地址輸出

  pf=(PF)(*(unsigned int*)pf);

  用來得到虛表里第一項的內(nèi)容,也就是output的地址(表第一項目地址=表地址)

  pf(); 調(diào)用函數(shù)

  我們來看結(jié)果

  成功了!!!

  雖然我們沒有在代碼里寫output();但執(zhí)行結(jié)果就是輸出了output的結(jié)果

  另外輸出的虛表地址就是0x0040c114,也就是我們最早推測的虛表地址!!!

  我把代碼改下一下,按照我們的推測,如果把表第一項地址偏移32位,應該就是表第二項地址,而第二項的內(nèi)容就應該是

  output2的地址,驗證一下:

  typedef void (__stdcall *PF)(void);

  void *pthis=&b;

  PF pf=(PF)(*(unsigned int*)pthis);

  printf("%x",pf);

  printf("n");

  pf=(PF)(*( (unsigned int*)pf-0x04 ) );

  pf();

  完全不出我們所料,輸出就是Class A output2

  到這里,應該對虛表的機制很清楚了,每個類都有各的虛表,每個類生成的各對象分別把this指向類的虛表地址,如果本類沒

  有重載基類的虛函數(shù),那么虛表的該項會寫為基類的該項的內(nèi)容,在調(diào)用虛表的時候,會根據(jù)虛表地址做適當?shù)钠埔缘玫?

  相應的虛函數(shù)地址,再進行調(diào)用.

  先分析到這,以后我會就修改虛表地址,以及如何應用虛表做hook,繼續(xù)分析

  


本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
鏈表基礎知識詳解(非常詳細簡單易懂)
對象布局已知時 C++ 對象指針的轉(zhuǎn)換時地址調(diào)整
C++多態(tài)實現(xiàn)原理 (虛函數(shù)和動態(tài)綁定)
指針
C語言學習教程第六章-指針(3)
匯編語言代碼重定位
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服