linux 虛函數調用性能,C++對象布局及多態實現探索之虛函數調用

我們再看看虛成員函數的調用。類C041中含有虛成員函數,它的定義如下:

struct C041

{

C041() : c_(0x01) {}

virtual void foo() { c_ = 0x02; }

char c_;

};

執行如下代碼:

C041 obj;

PRINT_DETAIL(C041, obj)

PRINT_VTABLE_ITEM(obj, 0, 0)

obj.foo();

C041 * pt = &obj;

pt->foo();

結果如下:

The detail of C041 is 14 b3 45 00 01

obj : objadr:0012F824 vpadr:0012F824 vtadr:0045B314 vtival(0):0041DF1E

我們打印出了C041的對象內存布局及它的虛表信息。

先看看obj.foo();的匯編代碼:

004230DF lea ecx,[ebp+FFFFF948h]

004230E5 call 0041DF1E

和前一篇文章中看過的普通的成員函數調用產生的匯編代碼一樣。這說明了通過對象進行函數調用,即使被調用的函數是虛函數也是靜態綁定,即在編譯時決議出函數的地址。不會有多態的行為發生。

我們跟蹤進去看看函數的匯編代碼。

01 004263F0 push ebp

02 004263F1 mov ebp,esp

03 004263F3 sub esp,0CCh

04 004263F9 push ebx

05 004263FA push esi

06 004263FB push edi

07 004263FC push ecx

08 004263FD lea edi,[ebp+FFFFFF34h]

09 00426403 mov ecx,33h

10 00426408 mov eax,0CCCCCCCCh

11 0042640D rep stos dword ptr [edi]

12 0042640F pop ecx

13 00426410 mov dword ptr [ebp-8],ecx

14 00426413 mov eax,dword ptr [ebp-8]

15 00426416 mov byte ptr [eax+4],2

16 0042641A pop edi

17 0042641B pop esi

18 0042641C pop ebx

19 0042641D mov esp,ebp

20 0042641F pop ebp

21 00426420 ret

值得注意的是第14、15行。第14行把this指針的值移到eax寄存器中,第15行給類的第一個成員變量賦值,這時我們可以看到在取變量的地址時用的是[eax+4],即跳過了對象布局最前面的4字節的虛表指針。

接下來我們看看通過指針進行的虛函數調用pt->foo();,產生的匯編代碼如下:

01 004230F6 mov eax,dword ptr [ebp+FFFFF900h]

02 004230FC mov edx,dword ptr [eax]

03 004230FE mov esi,esp

04 00423100 mov ecx,dword ptr [ebp+FFFFF900h]

05 00423106 call dword ptr [edx]

第1行把pt指向的地址移入eax寄存器,這樣eax中就保存了對象的內存地址,同時也是類的虛表指針的地址。第2行取eax中指針指向的值(注意不是 eax的值)到edx寄存器中,實際上也就是虛表的地址。執行完這兩條指令后,我們看看eax和edx中的值,果然和我們前面打印的obj的虛表信息中的 vpadr和vtadr的值是一樣的,分別為0x0012F824和0x0045B314。第4行同樣用ecx寄存器來保存并傳遞對象地址,即 this指針的值。第5行的call指令,我們可以看到目的地址不象通過對象來調用那樣,是一個直接的函數地址。而是將edx中的值做為指針來進行間接調用。前面我們已經知道edx中存放的實際是虛表的地址,我們也知道虛表實際是一個指針數組。這樣第5行的調用實際就是取到虛表中的第一個條目的值,即 C041::foo()函數的地址。如果被調用的虛函數對應的虛表條目的索引不是0,將會看到edx后加上一個索引號乘4后的偏移值。繼承跟蹤可以發現, ptr[edx]的值為0x0041DF1E,也和我們打印的vtival(0)的值相同。前面已經提到過,這個地址實際也不是真正的函數地址,是一個跳轉指令,繼續執行就到了真正的函數代碼部分(即前面列出的代碼)。

我們在上面看到的這個過程,就是動態綁定的過程。因為我們是通過指針來調用虛成員函數,所以會產生動態綁定,即使指針的類型和對象的類型是一樣的。為了保證多態的語義,編譯器在產生call指令時,不象靜態綁定時那樣,是在編譯時決議出一個確定的地址值。相反它是通過用發出調用的指針指向的對象中的虛指針,來迂回的找到對象所對應類型的虛表,及虛表中相應條目中存放的函數地址。這樣具體調用哪個函數就與指針的類型是無關的,只與具體的對象相關,因為虛指針是存放在具體的對象中,而虛表只和對象的類型相關。這也就是多態會發生的原因。

請回憶一下前面討論過的C071類,當子類重寫從父類繼承的虛函數時,子類的虛表內容的變化,及和父類虛表內容的區別(請參照第二篇中打印的子類和父類的虛表信息)。具體的通過指向子類對象的父類指針來調用被子類重寫過的虛函數時的調用過程,請有興趣的朋友自己調試一下,這里不再列出。

另外前面在《C++對象布局及多態實現之動態和強制轉換》中我們討論了指針的類型動態轉換。我們在這里再利用C041、C042及C051類,來看看指針的類型動態轉換。這幾個類的定義請參見前文。類C051從C041和C042多重繼承而來,且后兩個類都有虛函數。執行如下代碼:

C051 obj;

C041 * pt1 = dynamic_cast(&obj);

C042 * pt2 = dynamic_cast(&obj);

pt1->foo();

pt2->foo2();

第一個動態轉型對應的匯編代碼為:

00404B59 lea eax,[ebp+FFFFF8ECh]

00404B5F mov dword ptr [ebp+FFFFF8E0h],eax

因為不需要調整指針位置,所以很直接,取出對象的地址后直接賦給了指針。

第二個動態轉型牽涉到了指針位置的調整,我們來看看它的匯編代碼:

01 00404B65 lea eax,[ebp+FFFFF8ECh]

02 00404B6B test eax,eax

03 00404B6D je 00404B7D

04 00404B6F lea ecx,[ebp+FFFFF8F1h]

05 00404B75 mov dword ptr [ebp+FFFFF04Ch],ecx

06 00404B7B jmp 00404B87

07 00404B7D mov dword ptr [ebp+FFFFF04Ch],0

08 00404B87 mov edx,dword ptr [ebp+FFFFF04Ch]

09 00404B8D mov dword ptr [ebp+FFFFF8D4h],edx

代碼要復雜的多。&obj運算后得到的是一個指針,前三行指令就是判斷這個指針是否為NULL。奇怪的是第4行并沒有根據eax中的地址(即對象的起始地址)來進行指針的位置調整,而是直接把[ebp+FFFFF8F1h]的地址取到ecx寄存器中。第1行指令中的[ebp+ FFFFF8ECh]實際是得到對象的地址,ebp所加的那個數實際是個負數(補碼)也就是對象的偏移地址。對比兩個數發現相差5字節,這樣實際上第4行是直接得到了指針調整后的地址,即將指針指向了對象中的屬于C042的部分。后面的代碼又通過一個臨時變量及edx寄存器把調整后的指針值最終存到了 pt2指針中。

這些代碼實際可以優化成二行:

lea eax, [ebp+FFFFF8F1h]

mov dword ptr [ebp+FFFFF8d4h], eax

我們曾提到C051類有兩個虛表,相應對象中有也兩個虛表指針,之所以不合并為一個,就是為了處理指針的類型動態轉換。結合前面對于多態的討論,我們就可以理解得更清楚了。pt2->foo2();調用時,對象的類型還是C051,但經過指針動態轉換pt2指向了對象中屬于C042的部分的起始,也就是第二個虛表指針。這樣在進行函數調用時就不需要再做額外的處理了。我們看看pt1->foo();及pt2->foo2 ();產生的匯編碼即知。

01 00404B93 mov eax,dword ptr [ebp+FFFFF8E0h]

02 00404B99 mov edx,dword ptr [eax]

03 00404B9B mov esi,esp

04 00404B9D mov ecx,dword ptr [ebp+FFFFF8E0h]

05 00404BA3 call dword ptr [edx]

06 00404BA5 cmp esi,esp

07 00404BA7 call 0041DDDE

08 00404BAC mov eax,dword ptr [ebp+FFFFF8D4h]

09 00404BB2 mov edx,dword ptr [eax]

10 00404BB4 mov esi,esp

11 00404BB6 mov ecx,dword ptr [ebp+FFFFF8D4h]

12 00404BBC call dword ptr [edx]

13 00404BBE cmp esi,esp

14 00404BC0 call 0041DDDE

前7行為pt1->foo();,后7行為pt2->foo2();。唯一不同的是指針指向的地址不同,調用機制是一樣的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/445744.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/445744.shtml
英文地址,請注明出處:http://en.pswp.cn/news/445744.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

netflow流量分析工具 linux,Centos5/Linux安裝Nfdump和Nfsen圖形界面分析netflow數據

Nfdump是linux下netflow數據采集分析工具,Nfsen是基于nfdump是web界面工具,服務器需先安裝web服務器和php環境。安裝rrdtool及所需組件:yum install perl-rrdtool rrdtool rrdtool-devel rrdutils flex byacc安裝所需perl模塊:yum…

linux嵌入式平臺測試,protobuf-c 在arm linux 嵌入式平臺的使用 測試

關于什么是protobuf,網上搜搜一大堆,很多人用的都還是json,以為json是多種語言傳輸數據是萬能的,看完了protobuf的實現,就明白了簡單高效才是王道。1、首先寫一個.proto擴展名的文件json.proto,內容格式如下…

Linux gitpush錯誤,linux – GIT:無法推送(奇怪的配置問題)

我正在全新安裝Linux Mint.嘗試從任何存儲庫推送時,我收到以下錯誤:error: Malformed value for push.default: simpleerror: Must be one of nothing,matching,tracking or current.fatal: bad config file line 8 in /home/leng/.gitconfigfatal: Could not read …

linux+shell+func,Linux shell編程筆記總結

Linux Shell學習筆記簡介Linux系統的shell作為操作系統的外殼,為用戶提供使用操作系統的接口。它是命令語言、命令解釋程序及程序設計語言的統稱。shell是用戶和Linux內核之間的接口程序,如果把Linux內核想象成一個球體的中心,shell就是圍繞內…

linux版車機安裝步驟,RedHat Linux 9.0的安裝(詳細圖解安裝過程)

RedHat Linux版本:" b, t) b) b# }, t# z- fC& S$ x0 }) GRedHat Linux是目前世界上使用最多的Linux操作系統。因為它具備最好的圖形界面,無論是安裝、配置還是使用都十分方便,而且運行穩定,因此不論是新手還是老玩家都對它有很高的…

linux中網頁播放音樂,Linux_在Linux系統下播放網頁中的背景音樂技巧,在Linux中的firefox瀏覽許多網頁 - phpStudy...

在Linux系統下播放網頁中的背景音樂技巧在Linux中的firefox瀏覽許多網頁時,很多使用了基于WMP的背景音樂播放器,如部份baidu空間。但firefox默認不支持播放。在LINUX的源中,有一為kaffeine-mozilla-plugin,能在firefox中使用kaffe…

簡單了解linux,linux簡單了解

今天主要了解下linuxlinux目錄結構包含:/:代表根目錄bin(binaries):存放二進制可執行文件sbin(super user binaries):存放二進制可執行文件,只有root才能訪問etc(etcetera)存放系統配置文件usr(unix shared resource):用于存放共享…

C語言多個變量運算存儲過程,postgresql函數中的賦值運算和postgresql函數存儲過程實現數據批量插入...

今天檢查大家寫的postgresql函數時,發現有的家伙不遵守postgresql關于函數中的賦值運算表示方式:variable : value注意的是,是:表示賦值運算,而不是單獨一個等號。這一點和delphi相似?另外,postgresql中關于…

c語言盜取qq號程序,C++獲取本機登陸過的QQ號碼示例程序

// FileName: GetQQ.cpp#include "stdafx.h" // 如果編譯出錯請刪除這句#include "GetQQ.h"GetQQ::GetQQ(){}GetQQ::~GetQQ(){}std::vector GetQQ::Init(void){TCHAR pathBuffer[MAX_PATH] {0};::SHGetSpecialFolderPath(NULL, pathBuffer…

夫曼編碼譯碼系統課程設計實驗報告(含源代碼c++_c語言),哈夫曼編碼譯碼系統課程設計實驗報告(含源代碼C++_C語言)[1]...

目 錄摘 要 ………………………………………………………………………..……………… II Abstract …………………………………………………………………………..………... II 第一章 課題描述………………………..………………………………………………….. 1 1.1 問題描述………

二級c語言評分標準一樣嗎,計算機二級評分嚴格嗎 步驟錯了有分嗎

計算機二級考試不是人工閱卷,是由計算機評分,所以是很嚴格的。計算機二級機器閱卷主要看的是最后的輸出文件,如果步驟錯了導致結果不正確的話,也是沒有分的。計算機二級評分標準計算機二級考試是電腦自動評分的,即上機…

c語言a 尋路算法,JS/HTML5游戲常用算法之路徑搜索算法 A*尋路算法完整實例

本文實例講述了JS/HTML5游戲常用算法之路徑搜索算法 A*尋路算法。分享給大家供大家參考,具體如下:完整實例代碼如下:A*尋路算法#stage {border: 1px solid lightgray;}window.onload function () {var stage document.querySelector(#stage…

在wsl下運行c語言,在Windows10通過WSL架設linux/gcc c語言學習環境

在Windows10通過WSL架設linux/gcc c語言學習環境零:簡單介紹與先決條件中國一般的大學C課程中都使用很過時的編譯器來進行教授——比如說什么visual studio 6.0啊,dev c啊。以筆者的眼界所看到的C語言教程中,大多都建議學習者在Linux環境下學…

android二級聯動購物車,Android實現二級購物車的全選加反選、總價功能

本文實例為大家分享了Android實現二級購物車的全選加反選、總價的具體代碼,供大家參考,具體內容如下MainActivityimport android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.support.v7.widget.LinearLayoutManager;imp…

android studio gradle 自動更新,android studio gradle 兩種更新方法更新

又快一年沒有使用AS了,正好要查看一個Android項目代碼,干脆升級到新版試試看新變化,舊版本為3.1.2,目前最新版本為3.4.2,AS的升級是很簡單的直接update等待即可.升級完了,打開GIT下載的項目,啟動…

android安全 報告,Android安全檢測報告

1.高危 Intent Scheme URL攻擊詳情:惡意頁面可以通過Intent scheme URL執行基于Intent的攻擊建議:將Intent的component/selector設置為null2.高危 WebView應用克隆風險詳情:APP使用WebView訪問網絡,當開啟了允許JS腳本訪問本地文件…

android定時循環,Android AlarmManager實現定時循環后臺任務

這篇文章使用AlarmManager實現了Android定時后臺循環任務。使用場景:項目需要app每隔一段時間就去服務端請求一次接口,從而更新本地保存的信息。AlarmManager簡介AlarmManager是Android中常用的一種系統級別的提示服務,在特定的時刻為我們廣播…

android 線性布局蒙層,Android開發 - 掌握ConstraintLayout(一)傳統布局的問題

在傳統的Android開發中,頁面布局占用了我們很多的開發時間,而且面對復雜頁面的時候,傳統的一些布局會顯得非常復雜,每種布局都有特定的應用場景,我們通常需要各種布局結合起來使用來實現復雜的頁面。隨著ConstraintLay…

android sdk 4.4.4,4.4.4 not in Android SDK manager

問題I need to do some testing with 4.4.4 and it isnt available in my Android SDK Manager?Any ideas on what Im doing wrong?回答1:There is no problem at you Android SDK Manager, you just have to download the API 19.The API 19 is used by all the KitKat devi…

android7.0提示定位,解決android7.0上某些PopuWindow顯示位置不正確的問題

網上關于android系統7.0的popupwindow適配的解決方案,基本都是一樣的,就是重寫PopupWindow里面的方法但是如何進行重寫,對于一個初次接觸的人來說,是個很頭疼的問題。一來是涉及到java基礎,二來是涉及到popupwindow的源…