1. 程式人生 > >從彙編層面深度剖析C++虛擬函式

從彙編層面深度剖析C++虛擬函式

虛擬函式是C++語言實現執行時多型的唯一手段,因此掌握C++虛擬函式也成為C++程式設計師是否合格的試金石。csdn網友所發的一篇博文《VC虛擬函式佈局引發的問題》 從彙編角度分析了物件虛擬函式表的構,以及C++指標或者引用是如何利用這個表來實現執行時多型。

誠然,C++虛擬函式的結構會因編譯器不同而異,但所使用的原理是一樣的。為此,本文使用linux平臺下的g++編譯器,試圖從彙編的層面上分析虛擬函式表的結構,以及如何利用它來實現執行時多型。

組合語言是難讀的,特別是對一些沒有彙編基礎的朋友,因此,本文將彙編翻譯成相應的C語言,以方便讀者分析問題。

1. 程式碼

   為了方便表述問題,本文選取只有虛擬函式的兩個類,當然,還有它的建構函式,如下:

2. 兩個類的虛擬函式表(vtable)

使用g++ –Wall –S test.cpp命令,可以將上述的C++程式碼生成它相應的彙編程式碼。

_ZTV4Base是一個數據符號,它的命名規則是根據g++的內部規則來命名的,如果你想檢視它真正表示C++的符號名,可使用c++filt命令來轉換,例如:

[[email protected] ~]$ c++filt _ZTV4Base
vtable for Base

_ZTV4Base符號(或者變數)可看作為一個數組,它的第一項是0,第二項_ZIT4Base是關於Base的型別資訊,這與typeid有關。為方便討論,我們略去此二項資料。 因此Base類的vtable的結構,翻譯成相應的C語言定義如下:

而Derive的更是類似,只有稍為有點不同:

相應的C語言定義如下:

從上面兩個類的vtable可以看到,Derive的vtable中的第一項重寫了Base類vtable的第一項。只要子類重寫了基類的虛擬函式,那麼子類vtable相應的項就會更改父類的vtable表項。 這一過程是編譯器自動處理的,並且每個的類的vtable內容都放在資料段裡面。

3. 誰讓物件與vtable綁到一起

上述程式碼只是定義了每個類的vtable的內容,但我們知道,帶有虛擬函式的物件在它內部都有一個vtable指標,指向這個vtable,那麼是何時指定的呢? 只要看看建構函式的彙編程式碼,就一目瞭然了:

Base::Base()函式的編譯程式碼如下:

ZN4BaseC1Ev這個符號是C++函式Base::Base() 的內部符號名,可使用c++flit將它還原。C++裡的class,可以定義資料成員,函式成員兩種。但轉化到彙編層面時,每個物件裡面真正存放的是資料成員,以及虛擬函式表。

在上面的Base類中,由於沒有資料成員,因此它只有一個vtable指標。故Base類的定義,可以寫成如下相應的C程式碼:

建構函式中最關鍵的兩句是:

    movl    8(%ebp), %eax
    movl    $_ZTV4Base+8, (%eax)


$_ZTV4Base+8 就是Base類的虛擬函式表的開始位置,因此,建構函式對應的C程式碼如下:

同樣地,Derive類的建構函式如下:

4. 實現執行時多型的最關鍵一步

在造構函式裡面設定好的vtable的值,顯然,同一型別所有物件內的vtable值都是一樣的,並且永遠不會改變。下面是main函式生成的彙編程式碼,它展示了C++如何利用vtable來實現執行時多型。

 

    andl    $-16, %esp
    subl    $32, %esp

    這兩句是為區域性變數d和bp在堆疊上分配空間,也即如下的語句:

Derive d;  
Base *pb;

leal    24(%esp), %eax
movl    %eax, (%esp)
call    _ZN6DeriveC1Ev

esp+24是變數d的首地址,先將它壓到堆疊上,然後呼叫d的建構函式,相應翻譯成C語言則如下:

Derive::Dervice(&d);

leal    24(%esp), %eax
movl    %eax, 28(%esp)

這裡其實是將&d的值賦給pb,也即:

pb = &d;

最關鍵的程式碼是下面這一段:

翻譯成C語言也就傳神的那句:

pb->vtable[0](bp);

編譯器會記住f虛擬函式放在vtable的第0項,這是編譯時資訊。

5. 小結

這裡省略了很多關於編譯器和C++的細枝未節,是出於討論方便用的需要。從上面的編譯程式碼可以看到以下資訊:

1.每個類都有各有的vtable結構,編譯會正確填寫它們的虛擬函式表

2. 物件在建構函式時,設定vtable值為該類的虛擬函式表

3.在指標或者引用時呼叫虛擬函式,是通過object->vtable加上虛擬函式的offset來實現的。

當然這僅僅是g++的實現方式,它和VC++的略有不同,但原理是一樣的。

Locations of visitors to this page

相關推薦

彙編層面深度剖析C++虛擬函式

虛擬函式是C++語言實現執行時多型的唯一手段,因此掌握C++虛擬函式也成為C++程式設計師是否合格的試金石。csdn網友所發的一篇博文《VC虛擬函式佈局引發的問題》 從彙編角度分析了物件虛擬函式表的構

深度剖析c語言main函式---main函式的引數傳遞

  在上文中,我們主要講了c語言main函式的返回值問題,本文主要將main函式的引數傳遞。 main函式傳參   首先說明的是,可能有些人認為main函式是不可傳入引數的,但是實際上這是錯誤的。main函式可以從命令列獲取引數,從而提高程式碼的複用性。

深度剖析c語言main函式---main函式的返回值

  在接下來的幾篇博文中,我將對c語言的main函式進行深入的剖析,分別從函式返回值,函式引數,以及函式的執行順序對main函式作一總結。本文主要聊聊main函式的返回值。 main函式的返回值   main函式的返回值用於說明程式的退出狀態。如果返回0

深度剖析c語言main函式---main函式的執行順序

  在之前的文章中,介紹了main函式的返回值 和 main函式的傳參,本文主要介紹一下main函式的執行順序。可能有的人會說,這還用說,main函式肯定是程式執行的第一個函式。那麼,事實果然如此嗎?相信在看了本文之後,會有不一樣的認識。 為什麼說main

C++虛擬函式剖析

關鍵詞:虛擬函式,虛表,虛表指標,動態繫結,多型 一、概述 為了實現C++的多型,C++使用了一種動態繫結的技術。這個技術的核心是虛擬函式表(下文簡稱虛表)。本文介紹虛擬函式表是如何實現動態繫結的。 二、類的虛表 每個包含了虛擬函式的類都包含一個虛表。  我們知道,當一個

C++虛擬函式物件模型剖析

測試環境:VS2013 C++中的類物件模型有簡單的,也有複雜的。今天嘗試著剖析一下,以加深對繼承,虛擬函式等的理解。 從最簡單的繼承開始 class Base { public: int b;

深度剖析C++解構函式

建構函式和解構函式在C中意味著生命週期的開始和結束,它們的實現原理相同。由於解構函式往往還設定成虛擬函式,所以這裡我重點介紹下C解構函式的原理和各種場景。一、解構函式的作用 當物件的生命週期結束時,會自動呼叫解構函式,以清理一些資源,比如釋放記憶體、關閉檔案、關閉資料庫連

C++虛擬函式表以及記憶體對齊文章

C++虛擬函式表以及記憶體對齊文章 C++ 物件的記憶體佈局(上) https://blog.csdn.net/haoel/article/details/3081328 C++ 物件的記憶體佈局(下) https://blog.csdn.net/haoel/article/deta

c# 虛擬函式Virtual與重寫override

C#程式碼   using System; namespace Smz.Test { class A { public virtua

【轉】C++虛擬函式

引言 C++中的虛擬函式的作用主要是實現了多型的機制。關於多型,簡而言之就是用父類型別的指標指向其子類的例項,然後通過父類的指標呼叫實際子類的成員函式。這種技術可以讓父類的指標有“多種形態”,這是一種泛型技術。所謂泛型技術,說白了就是試圖使用不變的程式碼來實現可變的演算法。比如:模板技術,RTTI技術,虛擬函

C++虛擬函式表在虛繼承和繼承中的差別

下面的程式碼在gcc和VC中的結果 #include <cstdio> class A { public: virtual void funcaa() { printf("class A %s\n",__func__); } }; class AA:virtual pu

C++虛擬函式本質論

C++虛擬函式本質論 13.2.1 多型性原理 1.多型性概念 對多型性最簡單的理解就是一種事物有多種形態。在面向物件設計中,多型性指的是向不同的物件傳送同一訊息時,會產生不同的動作(或行為、功能)。所謂的“向不同的物件傳送同一訊息”,其實就是指呼叫不同物件的某個函式;而“產生不同的動作

C++ 虛擬函式的內部實現

單繼承的情況下 若類有虛擬函式,則在建構函式的時候編譯器會自動為類的例項(物件)在其記憶體的首部(0地址偏移處)增添一個虛擬函式表指標vfptr,指向該類的虛擬函式表。虛擬函式表中會存放該類所有的虛擬函式地址,普通函式則不會被放入其中。如果是子類重寫了父類的虛擬函式,那麼在建立虛擬函

c++虛擬函式(override)和過載函式(overload)的比較

1. 過載函式要求函式有相同的函式名稱,並有不同的引數序列;而虛擬函式則要求完全相同; 2. 過載函式可以是成員函式或友元函式,而虛擬函式只能是成員函式; 3. 過載函式的呼叫是以所傳遞引數的差別作為呼叫不同函式的依據,虛擬函式是根據物件動態型別的不同去呼叫不同

C++ 虛擬函式的兩個例子

1. 第一個例子是朋友告訴我Qt中的某個實現 1 #include <iostream> 2 3 // Qt中的某個實現 4 class A{ 5 public: 6 A() = default; 7 virtual ~A() = default; 8 9 virtua

深入淺出理解c++虛擬函式

    深入淺出理解c++虛擬函式   記得幾個月前看過C++虛擬函式的問題,當時其實就看懂了,最近筆試中遇到了虛擬函式竟然不太確定,所以還是理解的不深刻,所以想通過這篇文章來鞏固下。   裝逼一刻: 最近,本人思想發生了巨

理解C++虛擬函式

1、簡單介紹   C++虛擬函式是定義在基類中的函式,子類可以選擇重寫。在類中宣告(沒有寫函式體的為宣告)虛擬函式的格式如下: virtual void display(); 2、虛擬函式的作用   在使用指向子類物件的基類指標,並呼叫子

彙編層面分析if語句和switch的效能差異

1、if 語句原始碼 #include<iostream> int main() { int no = 4; if (no == 1) { printf("no is 1"); } else if (no == 2){ printf("no is 2"); } els

C++ 虛擬函式的預設引數問題

前些日子,有個同學問我一個關於虛擬函式的預設引數問題。他是從某個論壇上看到的,但是自己沒想通,便來找我。現在分享一下這個問題。先看一小段程式碼: #include <iostream> using namespace std; class A

C++虛擬函式表(含測試程式碼)

自己搞不懂C++虛擬函式之間的呼叫關係,特地花費一個下午加一個晚上查資料學習,現在把學到的發上來,供大家學習批評; 在此之前感謝這些大佬的部落格等,為我解惑甚多: 1、虛表與虛表指標 C++中的虛擬函式的實現一般是通過虛擬函式表(V-Table)來實