程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 解析動態聯編(上篇)

解析動態聯編(上篇)

編輯:關於C++

文章摘要

多態性是C++最主要的特征,多態性的實現得益於C++中的動 態聯編技術。文章通過對動態聯編的關鍵技術虛擬函數表進行深入的剖析,解析 的動態聯編的過程極其技術要領。

關鍵字

多態性 動態聯編 VTABLE 虛函數

文章正文

一 從多態性談動態聯編的必要性

在進入主題之前先介紹一下聯編的概念。聯編就是將模塊或者函數合並在一起生 成可 執行代碼的處理過程,同時對每個模塊或者函數調用分配內存地址,並且對 外部訪問也分配正確的內存地址。按照聯編所進行的階段不同,可分為兩種不同 的聯編方法:靜態聯編和動態聯編。在編譯階段就將函數實現和函數調用關聯起 來稱之為靜態聯編,靜態聯編在編譯階段就必須了解所有的函數或模塊執行所需 要檢測的信息,它對函數的選擇是基於指向對象的指針(或者引用)的類型。反 之在程序執行的時候才進行這種關聯稱之為動態聯編,動態聯編對成員函數的選 擇不是基於指針或者引用,而是基於對象類型,不同的對象類型將做出不同的編 譯結果。C語言中,所有的聯編都是靜態聯編。C++中一般情況下聯編也是靜態聯 編,但是一旦涉及到多態性和虛函數就必須使用動態聯編。

多態性是面向 對象的核心,它的最主要的思想就是可以采用多種形式的能力,通過一個用戶名 字或者用戶接口完成不同的實現。通常多態性被簡單的描述為"一個接口, 多個實現。在C++裡面具體的表現為通過基類指針訪問派生類的函數和方法。

下面我們看一個靜態聯編的例子,這種靜態聯編導致了我們不希望的結果 。

//1.cpp
1.#include <iostream.h>
2.class shape{
3.public:
4.void draw(){cout<<"I am shape"<<endl;}
5.void fun(){draw();}
6.};
7.class circle:public shape{
8.public:
9.void draw() {cout<<"I am circle"<<endl;}
10.};
11.main(){
12.class circle oneshape;
13.oneshape.fun();
14.}

程序的輸出結果我們希望是"I am circle",但事實上 卻輸出了"I am shape"的結果,造成這個結果的原因是靜態聯編。靜 態聯編需要在編譯時候就確定函數的實現,但事實上編譯器在僅僅知道shape的地 址時候無法獲取正確的調用函數,它所知道的僅是shape::draw(),最終結果只能 是draw操作束縛到shape類上。產生"I am shape"的結果就不足為奇了 。

為了能夠引起動態聯編,我們只需要將需要動態聯編的函數聲明為虛函 數即可。動態聯編只對虛函數起作用。我們在通過基類而且只有通過基類訪問派 生類的時候,只要這個基類中直接的或者間接(從上上層繼承)的包含虛函數, 動態聯編將自動喚醒。下面我們將上面的程序稍微改一下。

//2.cpp
1.#include <iostream.h>
2.class shape{
3.public:
4.virtual void draw(){cout<<"I am shape"<<endl;}
5.void fun(){draw();}
6.};
7.class circle:public shape{
8.public:
9.void draw() {cout<<"I am circle"<<endl;}
10.};
11.main(){
12.class circle oneshape;
13.fun (&oneshape);
14.}

程序執行得到了正確的結果"I am circle"。代碼在VC6.0中執行。

到目前為止我們不清楚動態聯編的 執行機制,但我們可以做個猜測。正如上面所說,對於函數的實際的對象類型不 同,聯編結果也應該不同。在靜態聯編中,執行的困難在於無法通過基類知道需 要聯編的子對象的確切類型。在1.cpp中shape的派生類既可能是circle,也可能是 其余的rectangle或者square等等,到底應該靜態聯編哪一個呢。迷惑正在於此。 動態聯編在編譯的時候應該也是不知道聯編的確切對象類型的,(如果知道的話 就成了靜態聯編了),因此它只能通過一定的機制,使得在執行時候能夠找到和 調用正確的函數體。可以想象,為了達到這個目的,一些相關信息應該封裝在對 象自身中。這些信息有點象身份證明,標識自己,這樣在動態聯編的時候,編譯 器可以根據這些標記找到相應的函數體,"不要跑,就是你了"。

實際上的動態聯編過程是什麼樣的呢。

二 對象類型信息

為了證明我們的猜想,我們用下面的一個程序進行測試,下面的程序將獲取普通 的類和包含虛函數的類的字節大小。程序代碼如下。

//3.cpp
1.#include <iostream.h>
2.class shape_novirtual{
3.int  a;
4.public:
5.void draw() {cout<<"shape_novirtual::draw()"<<endl;}
6.};
7.class shape_virtual1{
8.int  a;
9.public:
10.virtual void draw(){cout<<"shape_virtual::draw() "<<endl;}
11.};
12.class shape_virtual2{
13.int a;
14.public:
15.virtual void draw() {cout<<"shape_virtual2::draw()"<<endl;}
16.virtual void draw1(){cout<<"shape_virtual2::draw1() "<<endl;}
17.};
18.main(){
19.cout<<"sizeof(int)"<<sizeof(int) <<endl;
20.cout<<"sizeof(class shape_novirtual):"<<sizeof(shape_novirtual)<<endl;
21.cout<<"sizeof(void*):"<<sizeof(void*) <<endl;
22.cout<<"sizeof(class shape_virtual):"<<sizeof(shape_virtual)<<endl;
23.cout<<"sizeof(class shape_virtual2):"<<sizeof(shape_virtual2)<<endl;
24.}

VC6.0中運行結果如下:

sizeof(int)4
sizeof (class shape_novirtual):4
sizeof(void*):4
sizeof(class shape_virtual1):8
sizeof(class shape_virtual2):8
Press any key to continue

從上面可以看出,沒有虛函數的類shape_novirtual 的大小為4,正好為int a的大小。而帶有虛函數的類shape_virtual1和 shape_virtual2的大小除了int a的大小還多出了4格個字節的大小,這個大小正 好是void*指針的大小。到現在為止我們基本上可以說帶有虛函數的對象自身確實 插入了一些指針信息,而且這個指針信息並不隨著虛函數的增加而增大。

如果我們將每個類的成員變量int a去掉,VC6.0運行結果就會變成下面的情況。

sizeof(int)4
sizeof(class shape_novirtual):1
sizeof (void*):4
sizeof(class shape_virtual1):4
sizeof(class shape_virtual2):4
Press any key to continue

上面的運行結 果應該讓人感到例外。既然size(int)為4,現在沒有了這個成員變量,類 shape_novirtual應該字節大小為0,但事實上C++編譯器不允許對象為零長度。試 想一個長度為0的對象在內存中怎麼存放?怎麼獲取它的地址?為了避免這種情況 ,C++強制給這種類插入一個缺省成員,長度為1。如果有自定義的變量,變量將 取代這個缺省成員。

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