C++之this指標與另一種“多態”

來源:互聯網
上載者:User

C++之this指標與另一種“多態”

 

2010-7-9

燭秋

一、引入

  定義一個類的對象,首先系統已經給這個對象分配了空間,然後會調用建構函式(說明:假設存在建構函式--2010.9.5修正)。一個類有多個對象,當程式中調用對象的某個函數時,有可能要訪問到這個對象的成員變數。而對於同一個類的每一個對象,都是共用同一份類函數。對象有單獨的變數,但是沒有單獨的函數,所以當調用函數時,系統必須讓函數知道這是哪個對象的操作,從而確定成員變數是哪個對象的。這種用於對成員變數歸屬對像進行區分的東西,就叫做this指標。事實上它就是對象的地址,這一點從反組譯碼出來的代碼可以看到。

二、分析

1、測試代碼:

/////////////////////////////////////////////////////////////////////////////////////#include<iostream>usingnamespacestd;/////////////////////////////////////////////////////class A{public:A(char *szname){cout<<"construct"<<endl;name = new char[20];strcpy(name, szname);}~A(){cout<<"destruct"<<endl;delete name;}voidshow();private:char*name;};/////////////////////////////////////////////////////voidA::show(){cout<<"name = "<<name<<endl;}/////////////////////////////////////////////////////intmain(){A a("zhangsan");a.show();system("pause");return 0;}

程式在VC++6.0 32位作業系統上編譯、運行。

對編譯後的EXE檔案,進行反組譯碼。反組譯碼工具為OllyDbg。

2、反組譯碼分析

關鍵點如下:

(1)從圖1可以發現this指標通過ECX寄存器,傳遞給了成員函數。this指標就是對象的地址。

圖 1 Main函數

(2)從圖 2可以發現訪問對象的成員變數用的就是之前通過ECX傳入的this指標。

 

圖 2 show()函數

三、深入理解

       通過及相關的資料,可以很清晰的知道在調用建構函式、show()函數之前的那個ECX就是this指標,也就是說這是一個驗證性的實驗,答案已經很清楚了,所要做的就是去動手體驗一下。但是,假如我不懂C++、我不懂什麼this指標,我一樣可以發現這個叫做“this指標”的東西。通過OD的動態調試,當顯示出了name時,逐步回溯可以發現name的源頭是ECX。OD重新載入,查看在進入show()函數之前ECX是哪裡來的,最終可以一步步的發現,ECX就是一個地址,這個地址裡邊的第一個值也是一個地址,指向一串字串。再往上分析,進入show()上邊的建構函式,可以發現裡邊有new操作,strcpy操作,這裡就發現了字串空間、內容的來源。至此,基本就分析完了。

       通過這個過程可以發現很多C++的知識。如:對象的空間是在調用建構函式之前就分配好了的;對象裡邊沒有函數;this指標通過寄存器ECX傳遞;通過聲明定義的對象它的空間分配在棧中;等等這些跟系統或者C++有關聯的知識。

       但是,對於一個不懂C++的人看來,上面一段的體會都是沒有的。從彙編指令看不出C++的思想,this指標不過是一個地址;對象不過是一些空間;建構函式、解構函式以及其它的函數,也不過是一堆指令的集合。C++的同一個類定義出來的多個對象,從彙編指令看來是這樣的:有很多塊地址空間,它們有相同的大小。當不同的對象調用成員函數時,在彙編指令看來是:它們都call同一個地址,這個call指令其實裡邊是一個jmp指令,用於跳向某個位置,在call指令之前一般都會把一個地址放到ECX中,當然有時候會用堆棧或者其它寄存器。C++的繼承、多態、封裝,對組譯工具員來說是看不出有什麼神奇的,對於C++程式員來說那可就不同了,可以省去很多的工作,把很多事情都交給了編譯器,讓編譯器自動給你搞定。C++程式員所討論的對象及其眾多的特點、優點,最終還是變成了“低級”的指令,而且可能是效率低下的指令,即便如此,它的優點仍遠大於缺點,它讓編程變得容易、高效。

四、延伸

       忽然想到了C++的多態,一句話“將子類類型的指標賦值給父類類型的指標”,多態是通過虛函數實現。對虛函數及其相關內容的原理、詳細理解就不細說了。說下我的簡單理解,有一個基類A和子類B、C,有一個函數以基類A的指標為參數,然後在函數裡頭通過指標調用基類的成員函數。假如這個被調用的基類成員函數不是虛函數,那麼是不可能實現多態的,因為翻譯成彙編指令的時候,調用成員函數的這個地方是一個call指令,然後這個call指令跳到某個地方去執行,這是一個固定了的地址。通過定義為虛函數,調用成員函數的這個地方是通過虛函數表指標來確定調用哪個函數的,而虛函數表指標就放在對象的地址空間中,如果對象變了,那麼虛函數表指標也變了,調用的函數也就不同了。對於那個以基類A的指標為參數的函數,指標即是對象的地址,如果傳遞的地址是子類B或者C的對象的地址,那麼虛函數表指標也就不同了,調用的成員函數也就不同了。

這就是多態,這種多態使得調用同一個函數,因為傳遞參數的不同而顯示出差異,參數可以是基類對象或者眾多不同的子類對象。它們的差異是類與類之間的。

  有虛函數的對象的記憶體布局,比沒有虛函數的對象多了一個指向虛函數表的指標。因為虛函數的調用是通過虛函數表指標來實現的,所以有了多態。再考慮一下C++的this指標,一個類中的成員函數,依據this指標來區分不同的對象,也就是說根據this指標實現了訪問不同的對象的成員變數。

這是否也是多態的一種表現?這裡所說的多態已經不是那個“父類指標指向子類對象”的教條了,而是體現在同一個類的不同對象之間,調用同一個成員函數,依據參數“this指標”來實現訪問不同的對象的成員變數。成員函數訪問成員變數,在編譯期無法確定它訪問的成員變數在哪一個地址的,只有到了運行期依據this指標才能確定訪問的地址。這一點很類似於類的多態:以基類指標為參數的函數裡調用了某個基類的虛成員函數,在編譯期無法確定程式運行時調用的會是哪個類的對象,只有到了運行期才確定會調用哪個類的對象。

  this指標識別了同一個類的不同的對象,換句話說,this指標使得成員函數可以訪問同一個類的不同對象。再深入一點,this指標使得成員函數會因為this指標的不同而訪問到了不同的成員變數。這也是多態吧,只是它是必然存在的多態,這種多態跟基類與衍生類別之間的多態是不同層級的多態,它不像一般的多態可以通過對使用虛函數的選擇來取捨,它是一個類對應多個對象、多個對象共用一份成員函數代碼帶來的必然結果。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.