在c++中,指向類成員變數的指標儲存的並不是該成員變數所在記憶體的位址,而僅僅是該成員變數在該類物件中相對於物件首位址的偏移量。因此,它必須繫結到某乙個物件或者物件指標上面,這裡的物件和物件指標,就相當於充當了this指標的容器。
下面先看c++原始碼以及輸出結果:
#include #include using namespace下面是輸出的結果,由於執行結果在cmd上比較長,因此分開截圖:std;
class x ;
class y ;
class z : public x, public y
;int
main()
從上面的輸出結果我們可以得到下面三條資訊:
1 成員變數指標的大小為4byte
2 成員變數指標儲存的確是成員變數偏移所屬類物件首位址的偏移量,這可以通過下面的類的記憶體布局可以可能出來。但是有一點很奇怪,為什麼直接輸出&z::_y其值為0,但是輸出zmp2的時候值為4?這一點還不清楚,根據《深度探索c++物件模型》p133侯捷的譯註,似乎是編譯器做了處理,但這也只是猜測。
3 將基類成員指標繫結到派生類物件本身或者派生類物件指標上面,操作的仍是派生類物件裡面的對應的成員變數。
下面是類的繼承關係圖:
下面是每乙個類的記憶體布局:
下面來看看,為什麼當將基類成員變數指標繫結到派生類物件上的時候,操作的仍是派生類物件中對應的成員變數。就像下面的**:
z.*ymp1 = 6;
zp->*ymp1 = 8;成員變數指標ypm1本來儲存的是類z的基類y的成員_y的偏移量0,但是如果將它繫結到派生類z上,不管是用物件z本身或者是物件指標zp繫結,按理應該操作的是物件z中的_x成員,但是,
從輸出結果來看,仍然操作的是物件z中的_y成員變數。到底是什麼原因,下面是zp->*ymp1對應的彙編碼(z.*ymp1的原理一樣):
;下面是z.*ymp1對應的彙編碼:103 : zp->*ymp1 = 8;
cmp dword ptr _zp$[ebp], 0
;將zp指標的值和0比較,以防zp指標為空
je short $ln11@main;
如果上面的比較為0,就跳轉到標號$ln11@main處執行,否則順序執行 這裡順序執行
mov eax, dword ptr _zp$[ebp];
將物件z首位址(儲存在zp中)給暫存器eax
add eax, 4
;物件z的首位址加上4 得到是物件z中父類y物件的首位址,存於暫存器eax
mov dword ptr tv519[ebp], eax;
將父類y物件的首位址給臨時變數tv519
jmp short $ln12@main;
跳轉到標號$ln12@main處執行
$ln11@main:
mov dword ptr tv519[ebp], 0
;將0給臨時變數tv519
$ln12@main:
mov ecx, dword ptr tv519[ebp];
將臨時變數tv519的值給暫存器ecx 如果zp不為空,此時ecx中儲存的是父類y物件的首位址
add ecx, dword ptr _ymp1$[ebp];
將成員指標ymp1的值加上父類y物件的首位址,得到_y在物件z中的真正記憶體位址,存於ecx
mov dword ptr [ecx], 8
;將8寫入ecx儲存的記憶體單元裡面,即將物件z中的_y成員變數賦值為8
;可以看到,編譯器在內部實際上做了轉換 換成c++的表達形式即: zp ? (zp + sizeof(x)) : 0 zp + ymp1 也就是說編譯器內部先做了指標調整,使其指向了正確的父類物件首位址,然後再根據成員變數指標儲存的偏移量找到對應的成員變數。這裡,編譯器將zp的型別從z*轉換成了y*(向上轉換),這是允許的,但是,如果有乙個y* yp指標,這樣操作yp->*z***這樣是不允許的,因為這實際上是要將yp的型別從y*轉換成了z*(向下轉換)。92 : z.*ymp1 = 6;
lea edx, dword ptr _z$[ebp];
取物件z的首位址,存放到暫存器edx
test edx, edx;
測試edx儲存的值是否為0 即看物件的首位址是否為空
;下面的彙編**基本與zp->*ymp1 = 8的一樣
jeshort $ln3@main
leaeax, dword ptr _z$[ebp]
add eax, 4
movdword ptr tv410[ebp], eax
jmpshort $ln4@main
$ln3@main:
mov dword ptr tv410[ebp], 0
$ln4@main:
movecx, dword ptr tv410[ebp]
addecx, dword ptr _ymp1$[ebp]
mov dword ptr [ecx], 6
至於將zp指標分別轉化成xp和yp指標,在操作基類成員變數指標,原理和上面一樣,只不過轉換指標的過程由我們自己完成,即分別將zp的型別轉換成了x*和y*。
成員變數指標間的轉換是允許的,但是,只能由基類成員變數指標轉化到派生類成員變數指標,這是因為,基類中存在的成員變數,一定存在於派生類當中,所以,這種轉換是安全的,比如:
zmp2 = ymp1;//通過文章開始時c++程式的輸出結果,我們知道,ymp1的值為0,那麼,這樣轉換後,zmp2的值是多少呢,是0,還是4?下面來看這行**的彙編碼:zmp2指向派生類z裡面的成員變數y,ymp1指向基類y裡面的成員變數
;通過分析匯程式設計序,可以得知,zmp2的值不是0,而是4。也就是說,這裡的轉換並不是簡單的將ymp1裡面的偏移量賦給zmp2,而是編譯器內部做了轉化(這種轉化的效果和直接zmp2 = &z::_y)是一樣的,使得zmp2儲存的是成員變數y在派生類物件z裡面的偏移量4。這樣,當zmp2繫結到物件z或者其物件指標上時,操作的還是父類y子物件裡面的成員變數y。106 : zmp2 = ymp1;//zmp2指向派生類z裡面的成員變數y,ymp1指向基類y裡面的成員變數
cmp dword ptr _ymp1$[ebp], -1
;將ymp1的值和-1比較,如果ymp1等於-1,這說明ymp1還沒有指向任何成員變數
jne short $ln13@main;
如果ymp1不等於-1,就跳轉到標號$ln13@main處執行,否則,順序執行 這裡是順序執行(因為ymp1 = 0)
mov dword ptr tv532[ebp], -1
;將-1給臨時變數tv532
jmp short $ln14@main;
跳轉到標號$ln14@main處執行
$ln13@main:
mov edx, dword ptr _ymp1$[ebp];
將ymp1的值給暫存器edx
add edx, 4
;將暫存器edx裡面的值加4,此時edx裡面的值為4
;這也是上面判斷ymp1是否等於-1的原因,因為如果不做判斷,假如ymp1等於-1,即還沒指向任何成員變數
;這裡將得到錯誤的結果
mov dword ptr tv532[ebp], edx;
將暫存器edx裡面的值給臨時變數tv532
$ln14@main:
mov eax, dword ptr tv532[ebp];
將臨時變數tv532裡面的值給暫存器eax
mov dword ptr _zmp2$[ebp], eax;
將暫存器eax的值給zmp2
c 中指向類資料成員的指標
首先提出幾個問題 1 怎麼獲得資料成員的偏移量?2 如果類中有虛函式,類的布局是怎麼樣?vptr是放在物件記憶體的開始處還是結尾處,還是什麼地方?當然具體的編譯器實現不同 在這裡在vs2010上進行幾個簡單的測試 測試例子1 point3d.h檔案 pragma once class point3d...
從彙編看c 中成員函式指標 一
下面先來看c 的原始碼 include using namespace std class x virtual intget2 virtual intget3 int main 類x有3個成員函式,其中get1是普通的成員函式,而get2和get3都分別是虛成員函式。在main函式裡面分別定義了指向...
c 中指向函式的指標
函式指標是指指向函式而非指向物件的指標。像其他指標一樣,函式指標也指向某個特定的型別。函式型別由其返回型別以及形參表確定,而與函式名無關。int pi const string const string 這個語句將pi申明為指向函式的指標,它所指向的函式帶有兩個const string 型別的形參和...