浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?
?有下面的一個簡單的類:
class?CNullPointCall
{
public:
????static?void?Test1();
????void?Test2();
????void?Test3(int?iTest);
????void?Test4();
private:
????static?int?m_iStatic;
????int?m_iTest;
};
int?CNullPointCall::m_iStatic?=?0;
void?CNullPointCall::Test1()
{
????cout?<<?m_iStatic?<<?endl;
}
void?CNullPointCall::Test2()
{
????cout?<<?"Very?Cool!"?<<?endl;?
}
void?CNullPointCall::Test3(int?iTest)
{
????cout?<<?iTest?<<?endl;?
}
void?CNullPointCall::Test4()
{
????cout?<<?m_iTest?<<?endl;?
}
??? 那么下面的代碼都正確嗎?都會輸出什么?
CNullPointCall?*pNull?=?NULL;?//?沒錯,就是給指針賦值為空
pNull->Test1();?//?call?1
pNull->Test2();?//?call?2
pNull->Test3(13);?//?call?3
pNull->Test4();?//?call?4
??? 你肯定會很奇怪我為什么這么問。一個值為NULL的指針怎么可以用來調(diào)用類的成員函數(shù)呢?!可是實事卻很讓人吃驚:除了call 4那行代碼以外,其余3個類成員函數(shù)的調(diào)用都是成功的,都能正確的輸出結(jié)果,而且包含這3行代碼的程序能非常好的運行。
??? 經(jīng)過細(xì)心的比較就可以發(fā)現(xiàn),call 4那行代碼跟其他3行代碼的本質(zhì)區(qū)別:類CNullPointCall的成員函數(shù)中用到了this指針。
??? 對于類成員函數(shù)而言,并不是一個對象對應(yīng)一個單獨的成員函數(shù)體,而是此類的所有對象共用這個成員函數(shù)體。 當(dāng)程序被編譯之后,此成員函數(shù)地址即已確定。而成員函數(shù)之所以能把屬于此類的各個對象的數(shù)據(jù)區(qū)別開, 就是靠這個this指針。函數(shù)體內(nèi)所有對類數(shù)據(jù)成員的訪問, 都會被轉(zhuǎn)化為this->數(shù)據(jù)成員的方式。
??? 而一個對象的this指針并不是對象本身的一部分,不會影響sizeof(“對象”)的結(jié)果。this作用域是在類內(nèi)部,當(dāng)在類的非靜態(tài)成員函數(shù)中訪問類的非靜態(tài)成員的時候,編譯器會自動將對象本身的地址作為一個隱含參數(shù)傳遞給函數(shù)。也就是說,即使你沒有寫上this指針,編譯器在編譯的時候也是加上this的,它作為非靜態(tài)成員函數(shù)的隱含形參,對各成員的訪問均通過this進(jìn)行。
??? 對于上面的例子來說,this的值也就是pNull的值。也就是說this的值為NULL。而Test1()是靜態(tài)函數(shù),編譯器不會給它傳遞this指針,所以call 1那行代碼可以正確調(diào)用(這里相當(dāng)于CNullPointCall::Test1());對于Test2()和Test3()兩個成員函數(shù),雖然編譯器會給這兩個函數(shù)傳遞this指針,但是它們并沒有通過this指針來訪問類的成員變量,因此call 2和call 3兩行代碼可以正確調(diào)用;而對于成員函數(shù)Test4()要訪問類的成員變量,因此要使用this指針,這個時候發(fā)現(xiàn)this指針的值為NULL,就會造成程序的崩潰。????
??? 其實,我們可以想象編譯器把Test4()轉(zhuǎn)換成如下的形式:
void?CNullPointCall::Test4(CNullPointCall?*this)
{
????cout?<<?this->m_iTest?<<?endl;?
}
??? 而把call 4那行代碼轉(zhuǎn)換成了下面的形式:
CNullPointCall::Test4(pNull);
??? 所以會在通過this指針訪問m_iTest的時候造成程序的崩潰。
??? 下面通過查看上面代碼用VC 2005編譯后的匯編代碼來詳細(xì)解釋一下神奇的this指針。
??? 上面的C++代碼編譯生成的匯編代碼是下面的形式:
????CNullPointCall?*pNull?=?NULL;
0041171E??mov?????????dword?ptr?[pNull],0?
????pNull->Test1();
00411725??call????????CNullPointCall::Test1?(411069h)?
????pNull->Test2();
0041172A??mov?????????ecx,dword?ptr?[pNull]?
0041172D??call????????CNullPointCall::Test2?(4111E0h)?
????pNull->Test3(13);
00411732??push????????0Dh??
00411734??mov?????????ecx,dword?ptr?[pNull]?
00411737??call????????CNullPointCall::Test3?(41105Ah)?
????pNull->Test4();
0041173C??mov?????????ecx,dword?ptr?[pNull]?
0041173F??call????????CNullPointCall::Test4?(411032h)?
??? 通過比較靜態(tài)函數(shù)Test1()和其他3個非靜態(tài)函數(shù)調(diào)用所生成的的匯編代碼可以看出:非靜態(tài)函數(shù)調(diào)用之前都會把指向?qū)ο蟮闹羔榩Null(也就是this指針)放到ecx寄存器中(mov ecx,dword ptr [pNull])。這就是this指針的特殊之處。看call 3那行C++代碼的匯編代碼就可以看到this指針跟一般的函數(shù)參數(shù)的區(qū)別:一般的函數(shù)參數(shù)是直接壓入棧中(push 0Dh),而this指針卻被放到了ecx寄存器中。在類的非成員函數(shù)中如果要用到類的成員變量,就可以通過訪問ecx寄存器來得到指向?qū)ο蟮膖his指針,然后再通過this指針加上成員變量的偏移量來找到相應(yīng)的成員變量。
??? 下面再通過另外一個例子來說明this指針是怎樣被傳遞到成員函數(shù)中和如何使用this來訪問成員變量的。
??? 依然是一個很簡單的類:
class?CTest
{
public:
????void?SetValue();
private:
????int?m_iValue1;
????int?m_iValue2;
};
void?CTest::SetValue()
{
????m_iValue1?=?13;
????m_iValue2?=?13;
}
??? 用如下的代碼調(diào)用成員函數(shù):
CTest?test;
test.SetValue();
??? 上面的C++代碼的匯編代碼為:
????CTest?test;
????test.SetValue();
004117DC??lea?????????ecx,[test]?
004117DF??call????????CTest::SetValue?(4111CCh)?
??? 同樣的,首先把指向?qū)ο蟮闹羔樂诺絜cx寄存器中;然后調(diào)用類CTest的成員函數(shù)SetValue()。地址4111CCh那里存放的其實就是一個轉(zhuǎn)跳指令,轉(zhuǎn)跳到成員函數(shù)SetValue()內(nèi)部。
004111CC??jmp?????????CTest::SetValue?(411750h)
??? 而411750h才是類CTest的成員函數(shù)SetValue()的地址。
void?CTest::SetValue()
{
00411750??push????????ebp??
00411751??mov?????????ebp,esp?
00411753??sub?????????esp,0CCh?
00411759??push????????ebx??
0041175A??push????????esi??
0041175B??push????????edi??
0041175C??push????????ecx?//?1???
0041175D??lea?????????edi,[ebp-0CCh]?
00411763??mov?????????ecx,33h?
00411768??mov?????????eax,0CCCCCCCCh?
0041176D??rep?stos????dword?ptr?es:[edi]?
0041176F??pop?????????ecx?//?2?
00411770??mov?????????dword?ptr?[ebp-8],ecx?//?3
????m_iValue1?=?13;
00411773??mov?????????eax,dword?ptr?[this]?//?4
00411776??mov?????????dword?ptr?[eax],0Dh?//?5
????m_iValue2?=?13;
0041177C??mov?????????eax,dword?ptr?[this]?//?6
0041177F??mov?????????dword?ptr?[eax+4],0Dh?//?7
}
00411786??pop?????????edi??
00411787??pop?????????esi??
00411788??pop?????????ebx??
00411789??mov?????????esp,ebp?
0041178B??pop?????????ebp??
0041178C??ret?
??? 下面對上面的匯編代碼中的重點行進(jìn)行分析:
??? 1、將ecx寄存器中的值壓棧,也就是把this指針壓棧。
??? 2、ecx寄存器出棧,也就是this指針出棧。
??? 3、將ecx的值放到指定的地方,也就是this指針放到[ebp-8]內(nèi)。
??? 4、取this指針的值放入eax寄存器內(nèi)。此時,this指針指向test對象,test對象只有兩個int型的成員變量,在test對象內(nèi)存中連續(xù)存放,也就是說this指針目前指向m_iValue1。
??? 5、給寄存器eax指向的地址賦值0Dh(十六進(jìn)制的13)。其實就是給成員變量m_iValue1賦值13。
??? 6、同4。
??? 7、給寄存器eax指向的地址加4的地址賦值。在4中已經(jīng)說明,eax寄存器內(nèi)存放的是this指針,而this指針指向連續(xù)存放的int型的成員變量m_iValue1。this指針加4(sizeof(int))也就是成員變量m_iValue2的地址。因此這一行就是給成員變量m_iValue2賦值。
??? 通過上面的分析,我們可以從底層了解了C++中this指針的實現(xiàn)方法。雖然不同的編譯器會使用不同的處理方法,但是C++編譯器必須遵守C++標(biāo)準(zhǔn),因此對于this指針的實現(xiàn)應(yīng)該都是差不多的。
總結(jié)
以上是生活随笔為你收集整理的浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类的公有类型(public)和私有类型(
- 下一篇: CentOS下二进制包/源码安装方式的M