程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 探索VS中C++多態實現原理

探索VS中C++多態實現原理

編輯:C++入門知識

引言

最近把《深度探索c++對象模型》讀了幾遍,收獲甚大。明白了很多以前知其然卻不知其所以然的姿勢。比如構造函數與拷貝構造函數什麼時候被編譯器合成,虛函數、實例函數、類函數的區別等等。在此,我根據書本上的描述,結合VS2012的C++編譯器,來驗證其內容的正確性。讓我們一起以指針尋址、虛函數表等理論作為依據,以匯編代碼來實證,探索C++多態的實現。



    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    



談到多態,就不得不聊聊虛函數表了,有關虛函數表的內存布局請點擊這。但是虛函數表是怎麼創建的?又是怎麼使用的?虛函數表裡都是函數的指針,怎麼找到欲對應的函數指針?下面,我們從最基本的指針尋址開始,一步步解答這些問題。

指針類型的作用

當我們寫定義一個指針時,其對應的類型大小,便是其指向的內存范圍的大小,例如int *pi, pi指向的是一塊sizoef(int)大小的內存,char *pc;則指向的是一塊sizeof(char)大小的內存。為什麼要知道其指向的內存大小?舉個例子,如下圖:



    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    



運行時,可以觀察兩個指針指向的地址,及其指向地址處的內存數據:



    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    



Test類中,僅包含了四個int,此時sizeof(Test) == sizoef(int) * 4。注意,在此處,我們沒有為其定義構造函數以及copy構造函數。而且因為類中沒有virtual函數也沒有有構造函數的數據成員,編譯器也不會為這個類合成構造函數與copy構造函數。在上述最後一行代碼中,通過*pt取值,應該會有 4個類似mov dword ptr的操作。我們來看看生成的匯編代碼:



    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    


    



我們再來看看他們的地址:

image

這是神馬情況?我們明明把d的地址賦給了pAB呀!!!這不科學。我們一起去匯編代碼裡找原因去:

image

基本得到的信息與上面地址值對應:pAB中存的地址比d對象的地址大4個字節,然後以pAB中存的地址作為指針,輾轉兩次調用虛函數。很顯然,這個+4的操作是編譯器幫我們生成的,那麼為什麼要+4呢?我們來看一下派生類的對象內存布局:

image

這個對象有兩個虛函數表,畫出來應該是這樣:

image

作為對比,我們來看下Derived對象自己來調用虛函數時,是個什麼情況:

image

以Derived對象的指針來調用虛函數時,是個什麼情況:

image

在這裡,我們看到派生類對象無論是通過指針還是直接調用虛函數,其this參數,都會+4,即派生類包含該基類的內存布局的偏移。看來,調用虛函數時,總是以基類的“身份”去調用的。

現在,我們知道是如何調用到虛函數的了。但是還有個疑問:如果在派生類Derived中定義一個數據成員,並在虛函數中使用。那麼基類的指針,如何尋址到該地址呢?畢竟尋址范圍是根據指針類型來確定的。讓我們一起去探索這最後的疑問:

image

image

分析圖如下:

image

搞到最後,才發現調用虛函數的,其實一直是基類的指針。只是在對應的派生類虛函數實現裡,修正了基類指針相對於派生類對象首地址的偏移。

總結

要實現多態,需要這麼多的工具配合:虛表機制;派生類對象賦值給基類對象時,編譯器添加指針相對偏移代碼;虛函數內,編譯器生成相應的偏移指針調用派生類數據成員的代碼。多態,用起來輕快方便,實現起來難呀!

發現了個VS的bug,如果在調用虛函數時,this指針的值會在保存寄存器值後,無緣無故改變。變成對象的地址。其實是個假的值,查看內存即可知道。有可能是為了在調試時對用戶透明故意為之,以免普通用戶奇怪:居然this指針的值與對象值不同,囧…OrzEmbarrassed smile

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved