引言:近日
csdn的"c/c++語言"版的乙個問題 引 起了我的注意:"請問虛函式表放在**?"。我也曾經思考過這個問題,零零散散也有一定的收穫,這次正好趁這個機會把我對這一部分的理解整理一下。 首先值得宣告的是,本文的編譯環境是vs2002+winxp。c++標準並沒有對虛函式的實現作出任何的說明,甚至都沒有提到虛函式的實現需要用虛表來 實現,只不過主流的c++編譯器的虛函式機制都是通過虛表來實現的,所以用虛表來實現虛函式就成了"不是標準的標準"。但是這並不代表所有編譯器在實現細 節上的處理都是完全一致的,它們或多或少都存在一定的個體差異。所以,本文的結論不一定適用於其他的編譯情況。
虛函式/虛表的基礎知識
一 個類存在虛函式,那麼編譯器就會為這個類生成乙個虛表,在虛表裡存放的是這個類所有虛函式的位址。當生成類物件的時候,編譯器會自動的將類物件的前四個字 節設定為虛表的位址,而這四個位元組就可以看作是乙個指向虛表的指標。虛表裡依次存放的是虛函式的位址,每個虛函式的位址佔4個位元組。
編譯模組內部虛表存放的位置
const segment
??_7cderived@@6b@ ; cderived::`vftable
'dd flat:
?foobar@cderived@@uaexxz
dd flat:
?callme@cderived@@uaexxz
; function compile flags:
/odt
/rtcsu /zi
const ends
以上的彙編**給了我們這樣的資訊:
1> 虛表存放的位置應該實在模組的常量段中;
2> 這個類有兩個虛函式,它們分別是?foobar@cderived@@uaexxz和?callme@cderived@@uaexxz。
外部模組虛表存放的位置
當乙個模組匯出了乙個帶虛表的類,而另外乙個模組又使用了這個匯出類,這時候情況又是什麼樣的呢?這裡存在兩種很自然的處理方式:
1。維護乙份虛表。虛表放在定義匯出類的那個模組,任何使用這個匯出類的其他模組都要通過這個模組來使用匯出類。
2。維護多份虛表。這時候每乙個使用匯出類的模組都會有乙份虛表的拷貝。
vs2002是使用那一種情況呢?在假設存在多份虛表的前提下,我們可以使用這樣的策略來判斷vs2002使用那種方式:
1。在類定義模組中建立乙個類物件,並在另外乙個模組中使用這個類物件。在類定義模組中建立類物件保證編譯器用類定義模組中的虛表來初始化類物件。
2。在模組(非類定義模組)中建立並類物件並使用它。這樣就保證編譯器會用模組中的虛表來初始化類物件。
3。分別獲取兩種情況下兩個類物件的虛表指標。如果它們的值相等,就說明只存在乙份虛表;如果它們的值不等,就說明存在多份虛表。
4。如果兩個虛表指標的值相等,則虛表來自於兩個模組中的乙個模組,判斷這個虛表來自於那個模組。
cdllindepth
* pobjinanotherdll = createobject();
intvtableadress = *reinterpret_cast<
int*>(pobjinanotherdll);
intvfuncaddress = *reinterpret_cast<
intnew
cdllindepth;
intvtableadress2 = *reinterpret_cast<
intint
vfuncaddress2 = *reinterpret_cast<
int
對這段**做如下的解釋:
1。createobject()是dll匯出了乙個全域性函式。這個全域性函式實現的功能就是生成乙個類物件並將類物件的位址傳出。這樣做的目的就是為了在類定義模組中生成乙個類物件。
2。 獲得虛表指標和虛函式的**可以這樣分析:由於虛表指標存放在類物件的前4個位元組中,我們首先需要將類物件的首位址轉化成int型指標,並通過這個int 型指標獲得前4個位元組的內容,這個內容就是虛表的位址。接著我們將這個虛表的位址再轉化成int型指標,並通過這個int型指標獲得虛表的前4個位元組的內 容,這個內容就是虛表的第一項的值,也就是乙個虛函式的位址。
通過除錯,我們得出這樣的結果:
vtableadress
=0x1001401c
vfuncaddress
=0x1001103c
vtableadress2
=0x1001401c
vfuncaddress2
=0x1001103c
00400000
-00417000
dllindepth.dll
10000000
-10019000
後記 雖然本文的主要內容是討論虛表的位置,實際上本文涉及到dll匯出類的內容。在論壇上也經常看到一些網友對dll匯出類的內容感到迷惑。相對於簡單的函式 和資料,類的構成將顯得比較複雜,類宣告中可以包含任意型別的資料,成員函式,虛函式,靜態函式,我們就不禁迷惑這些東西是以什麼樣的方式匯出並讓其他的 模組使用的?對於這個問題,我不禁想到了乙個很有名的縮寫"
kiss(keep it ******, stupid) "。這是乙個很有用的思維方式,我們就不妨嘗試使用這種思維方法從簡單的出發點開始思考。對於dll來說,作為乙個模組,使用者感興趣的無非是**和資料:
在對c++類的結構(或者說模型)進行深入分析的基礎上,我們知道,對於c++類,它既有**,也有資料:
由此可見,從本質上來說,dll匯出類的情況就是匯出函式和資料,並沒有什麼神秘的。如果我們再加上類的特殊性的分析,問題的答案就清晰了:
對於成員函式,虛函式,靜態函式和靜態資料,他們都處於類的作用域內,所以他們匯出的函式符號中應該包含類的資訊。
對於成員函式和虛函式,他們的第乙個引數應該是指向類物件的指標,並且他們以"__thiscall"的呼叫習慣(calling convention)呼叫。
對於類的靜態函式和靜態資料,dll按照全域性函式和全域性資料的處理方式一樣處理他們。
虛表是以常量的形式匯出的。
^_^,dll匯出類的情況盡是如此的簡單,沒有想到吧,不過"情況就是這樣的"。
參考文獻
1.提到c++物件模型,就不得不提這本書:《深度探索c++物件模型 》。 對這本書的評價我就不羅嗦了,反正是只要涉及到c++物件模型的問題,很多人告訴你去看這本書就好了。相對於很多人這本書幾乎痴迷的崇拜,我保留我自己一 點小小的看法。c++物件模型的細節太依賴於c++編譯器,各個不同廠商的編譯器之間,甚至是同一廠商不同版本編譯器之間,都可能存在這樣或者那樣的差 別。對於不同的編譯器,我們還是要"就事論事",通過自己的實踐來獲得某個編譯器下的"第一手資料",而不能100%迷信書中的說法。
2.無意中發現一篇網友的blog文章,內容正好也是關於dll中匯出c++類,我發現寫的比我的詳細,對這個問題特別感興趣的朋友可以看看這篇文章:
balon白話msdn:從普通dll中匯出c++類(2) – 細看匯出c++類的底層機制
歷史記錄
01/28/2007 v1.0
原文的第一版
05/10/2007 v1.1
新增:在後記中新增了對dll匯出類的內容的分析
虛函式表存放位置
引言 近日 csdn的 c c 語言 版的乙個問題 引起了我的注意 請問虛函式表放在 我也曾經思考過這個問題,零零散散也有一定的收穫,這次正好趁這個機會把我對這一部分的理解整理一下。首先值得宣告的是,本文的編譯環境是vs2002 winxp。c 標準並沒有對虛函式的實現作出任何的說明,甚至都沒有提到...
C MFC VC 虛函式表指標的位置
編譯器會把一種叫虛指標 vptr 的隱藏資料插入到至少擁有乙個虛函式的類中。vptr 是一種指向虛函式位址列表的指標。在不同編譯器中,vptr 所在位置是不同的。一些編譯器 例如 visual c 和 c builder 把 vptr 放置在類的開頭部分,在所有使用者宣告的資料成員的前面。而另一些編...
虛函式表指標,虛函式表
對c 了解的人都應該知道虛函式 virtual function 是通過一張虛函式表 virtual table 來實現的。簡稱為v table。在這個表中,主是要乙個類的虛函式的位址表,這張表解決了繼承 覆蓋的問題,保證其容真實反應實際的函式。這樣,在有虛函式的類的例項中這個表被分配在了 這個例項...