我們知道C++支援的虛函數是通過為每個類提供一個虛函數來實現的,它的記憶體狀態究竟是怎樣的呢?
首先我們實現兩個擁有虛函數的類:ClassA和ClassB,其中ClassB是ClassA的衍生類別,具體實現如下:
//ClassA
#include <iostream.h>
#include <stdio.h>
class ClassA
{
public:
ClassA()
{
x=0;
y=0;
}
virtual void Func1(void)
{
printf("This is ClassA::Func1( )/n");
return;
}
virtual void Func2(void)
{
printf("This is ClassA::Fun2( )/n");
return;
}
int x;
int y;
};
//ClassB
#include <iostream.h>
#include <stdio.h>
class ClassB:public ClassA
{
public:
virtual void Func2(void)
{
printf("This is ClassB::Func2()/n");
return;
}
virtual void Func3(void)
{
printf("This is ClassB::Func3()/n");
return;
}
};
容易看出ClassA的成員包括
虛函數: ClassA::Func1( ); ClassA::Func2( );
成員變數: int x; int y;
ClassB的成員包括
虛函數: ClassB::Func2( ); ClassB::Func3( );
成員變數: int x; int y; (由ClassA繼承的)
而且ClassB重載了ClassA的第二個虛函數,自己實現了一個虛函數Func3( )。
因而我們知道ClassA的對象的記憶體結構可如下表示:
&ClassA::Func1( )
<!--[if !vml]--><!--[endif]-->&ClassA::Func2( )
ClassA::
Func1( )
{……}
<!--[if !vml]--><!--[endif]-->
<!--[if !vml]--><!--[endif]-->ClassA:
VTable
X
Y
ClassA::
Func2( )
{……}
ClassB對象的結構是類似的,但是要注意ClassB::Func2()是繼承ClassA的。
下面先考慮怎麼樣才能得到VTbale 呢?
首先在程式中聲明一個ClassA的對象
ClassA caa; //事實上我們已經可以得到caa的地址了
int *pInta=NULL;
int *pCaa=(int *)(&caa); //指向對象caa的指標
int * const &pVtable=(int *)(*pCaa); //聲明一個指向int型指標的引
//用,並用caa對象的前4個字
//節的內容初始化;
void *pVoid;
void (*pFun1)(void);
void (*pFun2)(void);
void (*pFun3)(void);
好了,我們知道在對象caa記憶體空間的前面4個位元組存放的就是ClassA類的虛函
數表的指標,而我們已經得到了,就是pVtable了。
可以用下面的語句來驗證一下:
pInta=(int *)(&caa);
printf("The memory Content of Class caa:/n");
printf("/t vtable : %x/n",*(pInta));
printf("/t x : %d/n",*(pInta+1));
printf("/t y : %d/n",*(pInta+2));
恩,啟動並執行結果如下:
The memory Content of Class caa:
vtable : 415394
x : 0
y : 0
成功的取得了caa對象的Vtable之後,我們的工作就是通過Vtable來得到
虛函數表了。
這已經是很簡單了:
pVoid=(int *) (*pVtable);
pFun1=(void (*)(void))pVoid; //這就是指向第一個虛函數函數
//程式碼片段的指標了
pVoid=(int *) (*(pVtable+1));
pFun2=(void)(*)(void))pVoid; //這就是指向第二個虛函數函數
//程式碼片段的指標了
然後我們執行通過這兩個指標來執行一下函數以便驗證我們確實取得了想要
虛函數的指標。
pFun1();
pFun2();
可以得到輸出為:
This is ClassA::Func1( )
This is ClassA::Func2( )
當然,我們也可以列印出這幾個虛函數的地址,這樣就可以和ClassB的VTable
相比較了(J) 。
同樣的道理可以得到ClassB的虛函數表,如果我們列印出ClassA和ClassB
的各個虛函數的函數地址的話,很容易發現ClassB的Vtable的第一個指標——
指向ClassB的第一個虛函數的程式碼片段,是和ClassA的Vtable的第一個指標的內
容是相同的。哈哈,因為ClassB是繼承ClassA的,而且在ClassB中我們並沒有
重載函數 virtual ClassA:Finc1( );事實上它們指向的是同一個函數體的。
注釋:程式的運行環境Intel 80X86/WinXP/Visual C++6.0,在程式中,我們使用了
int * pInt; void (*)(void) ; void *pVoid ;int;等指標和類型對記憶體進行了頻
繁的轉換,因為在我們的環境中
sizeof(int)==sizeof(int *)=sizeof(void (*)(void))==4;
也就是這些類型都佔用了4個位元組的記憶體單位,因而在他們之間的轉換並沒有引
起記憶體泄露等等錯誤,正如我們確實作到的,呵呵!
其次就是我們使用了指標引用
int * const &pVtable=(int *)(*pCaa);
而沒有簡單使用
int *pInt ; 指向相同的內容呢?
這主要是因為在後面我們要用到
pVoid=(int *) (*(pVtable+1));
來取得第i個虛函數的指標,如果簡單的應用
pVoid=(int *)(*(pInt+1));
的話就達不到目標了,看到其中的差別了嗎?
對了,就是因為指標變數pInt本身在記憶體中的位置已經和VTable沒有什麼關係了
(當然,這麼說多少有點武斷J,因為畢竟二者都是在main 函數的運行棧中),
雖然pInt和VTable中的內容是一樣的,但是pInt++返回的將是pInt在記憶體中的
位置的下一個位置,是得不到下一個虛函數的指標的。那(*pInt)++得到的是什麼
呢?不要認為是指向第二個虛函數的指標啊,這條語句所做的不過是將由pInt存
儲的第一個虛函數的代碼地址再自加而已,如此一來,我們不但得不到第二個虛
函數的地址,連第一個已得到的虛函數的地址都被破壞了!!
再次,
int * const &pVtable=(int *)(*pCaa);
為什麼要用const 修飾呢?注意我們取得的(*pCaa)返回的已經不是一個變數了,
而是一個右值,在C++ 中,允許定義一個常量的引用,過程是先使用常量構造一
個臨時變數(該臨時變數的生命週期和將要定義的引用變數一致),再用該臨時變
量來初始化我們定義的引用變數,前提是必須要使用const 修飾,這樣就防止了
臨時變數被使用者修改了,而這通常是不適合的。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/theCFan/archive/2005/04/24/360605.aspx