從彙編看c 中指向成員變數的指標 一

2022-05-15 09:41:55 字數 4360 閱讀 1778

在c++中,指向類成員變數的指標儲存的並不是該成員變數所在記憶體的位址,而僅僅是該成員變數在該類物件中相對於物件首位址的偏移量。因此,它必須繫結到某乙個物件或者物件指標上面,這裡的物件和物件指標,就相當於充當了this指標的容器。

下面先看c++原始碼以及輸出結果:

#include #include using namespace 

std;

class x ;

class y ;

class z : public x, public y

;int

main()

下面是輸出的結果,由於執行結果在cmd上比較長,因此分開截圖:

從上面的輸出結果我們可以得到下面三條資訊:

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的原理一樣):

;

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

下面是z.*ymp1對應的彙編碼:

;

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

可以看到,編譯器在內部實際上做了轉換 換成c++的表達形式即: zp ? (zp + sizeof(x)) : 0  zp + ymp1 也就是說編譯器內部先做了指標調整,使其指向了正確的父類物件首位址,然後再根據成員變數指標儲存的偏移量找到對應的成員變數。這裡,編譯器將zp的型別從z*轉換成了y*(向上轉換),這是允許的,但是,如果有乙個y* yp指標,這樣操作yp->*z***這樣是不允許的,因為這實際上是要將yp的型別從y*轉換成了z*(向下轉換)。

至於將zp指標分別轉化成xp和yp指標,在操作基類成員變數指標,原理和上面一樣,只不過轉換指標的過程由我們自己完成,即分別將zp的型別轉換成了x*和y*。

成員變數指標間的轉換是允許的,但是,只能由基類成員變數指標轉化到派生類成員變數指標,這是因為,基類中存在的成員變數,一定存在於派生類當中,所以,這種轉換是安全的,比如:

zmp2 = ymp1;//

zmp2指向派生類z裡面的成員變數y,ymp1指向基類y裡面的成員變數

通過文章開始時c++程式的輸出結果,我們知道,ymp1的值為0,那麼,這樣轉換後,zmp2的值是多少呢,是0,還是4?下面來看這行**的彙編碼:

;

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

通過分析匯程式設計序,可以得知,zmp2的值不是0,而是4。也就是說,這裡的轉換並不是簡單的將ymp1裡面的偏移量賦給zmp2,而是編譯器內部做了轉化(這種轉化的效果和直接zmp2 = &z::_y)是一樣的,使得zmp2儲存的是成員變數y在派生類物件z裡面的偏移量4。這樣,當zmp2繫結到物件z或者其物件指標上時,操作的還是父類y子物件裡面的成員變數y。

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 型別的形參和...