關(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的地址

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

而基址指針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指針的首地址。


從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í)使用。