關(guān)于C++ 的 this指針

關(guān)于this指針

在編程過程中,我們都使用過this指針,可是this指針究竟從何而來卻很少有人知道,現(xiàn)在我們一起來看一下this指針的由來

測(cè)試代碼如下:

class CTest
{
public:
    void SetNumber(int number)
    {
        m_nInt = number;
    }

    int m_nInt = 0;
};

int main(int argc, char *argv[])
{
    CTest test;
    test.SetNumber(5);
    printf("CTest : m_nInt = %d\n", test.m_nInt);

    system("pause");
    return 0;
}

對(duì)編譯生成的程序進(jìn)行反匯編:

main函數(shù)部分的匯編代碼如下

int main(int argc, char *argv[])
{
01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  
01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  
01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  
    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  
    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  
    printf("CTest : m_nInt = %d\n", test.m_nInt);
01183D0A  mov         esi,esp  
01183D0C  mov         eax,dword ptr [test]  
01183D0F  push        eax  
01183D10  push        1185858h  
01183D15  call        dword ptr ds:[1189118h]  
01183D1B  add         esp,8  
01183D1E  cmp         esi,esp  
01183D20  call        __RTC_CheckEsp (01181140h)  

    system("pause");
01183D25  mov         esi,esp  
01183D27  push        1185874h  
01183D2C  call        dword ptr ds:[1189110h]  
01183D32  add         esp,4  
01183D35  cmp         esi,esp  
01183D37  call        __RTC_CheckEsp (01181140h)  
    return 0;
01183D3C  xor         eax,eax  
}
01183D3E  push        edx  
01183D3F  mov         ecx,ebp  
01183D41  push        eax  
01183D42  lea         edx,ds:[1183D70h]  
01183D48  call        @_RTC_CheckStackVars@8 (01181087h)  
01183D4D  pop         eax  
01183D4E  pop         edx  
01183D4F  pop         edi  
01183D50  pop         esi  
01183D51  pop         ebx  
01183D52  mov         ecx,dword ptr [ebp-4]  
01183D55  xor         ecx,ebp  
01183D57  call        @__security_check_cookie@4 (0118101Eh)  
01183D5C  add         esp,0D0h  
01183D62  cmp         ebp,esp  
01183D64  call        __RTC_CheckEsp (01181140h)  
01183D69  mov         esp,ebp  
01183D6B  pop         ebp  
01183D6C  ret  

我們先簡(jiǎn)單理解一下上述的匯編代碼:

01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  

這是main函數(shù)中最開頭的一部分代碼,主要用于利用棧來保護(hù)現(xiàn)場(chǎng),保存外部調(diào)用函數(shù)的基址(ebp寄存器中),并將其設(shè)置當(dāng)前函數(shù)的基址,然后保存之后要用到的幾個(gè)寄存器中的數(shù)據(jù),這樣當(dāng)函數(shù)返回后,保證其調(diào)用函數(shù)可以繼續(xù)正常向下執(zhí)行。并將棧指針寄存器esp向棧低地址移動(dòng), 這主要用于開辟??臻g給當(dāng)前函數(shù)塊中使用。

01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  

這部分代碼主要用于初始化當(dāng)前的??臻g,將esp 至 ebp 之間的內(nèi)存全部初始化為0xCC(0xCC 表示當(dāng)前內(nèi)存暫未使用過)。

01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  

這段代碼主要在基址指針上的前四個(gè)字節(jié)插入隨機(jī)數(shù),之后會(huì)利用這個(gè)隨機(jī)數(shù)進(jìn)行檢查,防止棧溢出攻擊。

    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  

這里很明顯開始調(diào)用CTest的構(gòu)造函數(shù),跟進(jìn)去

CTest::CTest:
011811E5  jmp         CTest::CTest (011813D0h)  

發(fā)現(xiàn)這里執(zhí)行的是一次跳轉(zhuǎn),再跟:

CTest::CTest:
011813D0  push        ebp  
011813D1  mov         ebp,esp  
011813D3  sub         esp,0CCh  
011813D9  push        ebx  
011813DA  push        esi  
011813DB  push        edi  
011813DC  push        ecx  
011813DD  lea         edi,[ebp-0CCh]  
011813E3  mov         ecx,33h  
011813E8  mov         eax,0CCCCCCCCh  
011813ED  rep stos    dword ptr es:[edi]  
011813EF  pop         ecx  
011813F0  mov         dword ptr [this],ecx  
011813F3  mov         eax,dword ptr [this]  
011813F6  mov         dword ptr [eax],0  
011813FC  mov         eax,dword ptr [this]  
011813FF  pop         edi  
01181400  pop         esi  
01181401  pop         ebx  
01181402  mov         esp,ebp  
01181404  pop         ebp  
01181405  ret  

這里可以看出是由編譯器提供的默認(rèn)構(gòu)造函數(shù)的匯編指令,并在此處將m_nInt 初始化為0了,可見在類中初始化的變量,即使不是在初始化函數(shù)中初始化的成員變量,最后還是在構(gòu)造函數(shù)中初始化。

    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  

lea 為加載地址指令
這里我們看一下test的地址


02.png

test的地址為0x0044fd74,在內(nèi)存中此時(shí)成員m_nInt已被初始化為0了(如下圖):

01.png

而基址指針ebp為0x0044fd80,比test的首地址大12(8+4),即對(duì)象的大小+隨機(jī)數(shù)保存位置的內(nèi)存大小。
這里是VS的優(yōu)化后顯示的結(jié)果,實(shí)際應(yīng)該是 [test] 等價(jià)于 [ebp - 12]

所以在調(diào)用函數(shù)之前不但傳入了5,還通過ecx (隱含地)傳入了對(duì)象的首地址,即所謂的“this指針”

接著看函數(shù)內(nèi)部的實(shí)現(xiàn),以證明上述觀點(diǎn)!

同理由此jmp跳轉(zhuǎn),直接看內(nèi)部實(shí)現(xiàn):

    void SetNumber(int number)
    {
01181420  push        ebp  
01181421  mov         ebp,esp  
01181423  sub         esp,0CCh  
01181429  push        ebx  
0118142A  push        esi  
0118142B  push        edi  
0118142C  push        ecx  
0118142D  lea         edi,[ebp-0CCh]  
01181433  mov         ecx,33h  
01181438  mov         eax,0CCCCCCCCh  
0118143D  rep stos    dword ptr es:[edi]  
0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  
        m_nInt = number;
01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  
    }
0118144B  pop         edi  
0118144C  pop         esi  
0118144D  pop         ebx  
0118144E  mov         esp,ebp  
01181450  pop         ebp  
01181451  ret         4  

這段代碼一直到 0118143D所在行都是日常任務(wù),開辟并初始化??臻g。

0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  

從這行開始真正的工作了,這里將外部傳進(jìn)來得對(duì)象首地址存進(jìn)了 “this”中,我們查看一下this的首地址是多少
在當(dāng)前ebp之上,保存著ecx中傳入的0x0044fd74的值之處就是this指針的首地址。

03.png
04.png

從04圖中可以看到0x0044FC90 即為棧上為this分配的內(nèi)存的首地址,這里將ecx中保存的對(duì)象所在的首地址(即指針)保存在了為this所分配的內(nèi)存中,指針本身占用4個(gè)字節(jié),由于內(nèi)存對(duì)齊,此處編譯器分配了8個(gè)字節(jié)。0x0044FC90 + 8 正好等于ebp中保存的值。

01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  

再看看[number] 指代的是什么(當(dāng)然是形參number啦,但實(shí)際反匯編時(shí)不會(huì)這么明顯,我們來看看實(shí)際到底指代的是什么)

由前面代碼推得,在進(jìn)入函數(shù)前,5已經(jīng)壓人棧中,所以5的地址必然大于當(dāng)前的ebp的值,由圖4可見number的地址為ebp+8。

ebp為首地址的指針為調(diào)用該函數(shù)的函數(shù)的棧的基址。
ebp + 4 為調(diào)用該函數(shù)處的下一行指令的的地址,用于ret 返回時(shí)使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容