深入探讨this指针
深入探討this指針
?
為了寫這篇文章,準(zhǔn)備了好長時(shí)間,翻遍了箱底的書籍??墒侨缃襁€是不敢放開手來寫,戰(zhàn)戰(zhàn)兢兢。不是操心自己寫錯(cuò),而是唯恐自己錯(cuò)誤誤導(dǎo)別人。同一時(shí)候也希望這篇文章能給你一點(diǎn)收獲。既然是深入探討this指針,所以建議剛開始學(xué)習(xí)的人,最好具有一定編譯基礎(chǔ),調(diào)試基礎(chǔ)。假設(shè)大家覺得這片文章有不滿的地方,就給我發(fā)信批評(píng)一下,以便及時(shí)修正。
關(guān)于this指針的描寫敘述我們一般從語言層次上講;
this指針作為一個(gè)隱含參數(shù)傳遞給非靜態(tài)成員函數(shù),用以指向該成員函數(shù)所屬類所定義的對(duì)象。當(dāng)不同的對(duì)象調(diào)用同一個(gè)類的成員函數(shù)代碼時(shí),編譯器會(huì)根據(jù)該成員函數(shù)的this指針?biāo)赶虻牟煌瑢?duì)象來確定應(yīng)該引用哪個(gè)對(duì)象的數(shù)據(jù)成員。簡單樣例
我們定義一個(gè)簡單stack類
// 定義stack類
class Stack
{
public:
???? Stack();// 構(gòu)造函數(shù)
???? ~Stack();// 析構(gòu)函數(shù)
public:
???? void push(char c);// 壓棧函數(shù)
private:
???? char *top;// 棧頂元素
???? char *max;// 棧容量
};
?
// 壓棧函數(shù)
void Stack::push(char c)
{
???? if(top > max)
???? {
???????? ERROR;
???? }
???? *top++ = c;
}
?
// 定義公共函數(shù),操作棧對(duì)象中的push函數(shù)
void FunStack(Stack *p)
{
???? p->push('c');
}
上面的代碼我們?cè)黾?span lang="en-us">this概念,以C代碼形式顯示(你能夠理解編譯C++成C代碼后,Cfront開始就是這么做的)
// 用普通C描寫敘述類成員函數(shù)
void Stack__push(this,c);// 普通C代碼
{
???? if(this->top > this->max)
???? {
???????? ERROR;
???? }
???? *(this->top)++ = c;
}
?
void FunStack(p)// Stack *p;
{
???? Stack__push(p,'c');
}
C++中this指針是從Simula(僅僅是聽說沒有使用過)里的THIS引用的翻版,有時(shí)候有人會(huì)問,為什么this是指針而不是一個(gè)引用?為什么叫this而不是叫self(smalltalk)?第一個(gè)問題是,當(dāng)this引入帶類的C時(shí),在那時(shí)的是C++中還沒有引用機(jī)制,所以僅僅能是this指針而不是引用了。第二個(gè)問題,更簡單了,就是由于this是從simula來,而不是從smalltalk來。
??? 上面是簡單的討論,我們將逐步深入討論this。
我們通過this訪問對(duì)象(已經(jīng)成慣例了)中函數(shù)和變量時(shí)一般這樣使用
??? this->top;// 訪問變量
??? this->push();// 訪問函數(shù)
?
??? (*this).top;// 訪問變量
??? (*this).push();// 訪問函數(shù)
通過上面樣例,我們從語言層次上說this是一個(gè)指針(或許你說this本來就是一個(gè)指針,就叫this指針,不要著急聽我慢慢說來)。那么this是一個(gè)什么樣子的指針,比方我們最常見的指針有。
??? int *p;
??? Const int *p;
??? int * const p;
那么this指針是不是當(dāng)中一種?以下我們分別驗(yàn)證。
??? 我們定義類,作為驗(yàn)證對(duì)象
??? class A
{
public:
???? int iData;// 簡單期間我們定義為int型
???? mutable int iData2;// mutable變量
int Fun1(){return ++iData;};// 普通函數(shù)㈠
???? int Fun2() const {return ++iData;};// 帶const的函數(shù)㈡
};
上面的㈠函數(shù)能夠正確運(yùn)行。
上面㈡函數(shù),不能通過編譯,我們知道在const函數(shù)中,不同意改動(dòng)類中變量。那么終于原因是什么?事實(shí)上在上面的樣例中,我們用C實(shí)現(xiàn)
int A_Fun2(const A* this);
const函數(shù)本質(zhì)是const this的原因,所以不同意改動(dòng)iData值。
至少如今我們能夠確定this指針,不是一個(gè)const常量指針。由于假設(shè)this是常量指針,我們就不能改動(dòng)類中變量的值了。捎帶我們提一下C++中keywordmutable,如上定義的mutable int iData2;// mutable變量,這樣我們就能夠在const函數(shù)中改動(dòng)iData2的值。事實(shí)上這時(shí)的mutable和public,private,protected是同樣的,這些keyword僅僅是在編譯時(shí)刻實(shí)用,編譯后變量類型是沒有差別的。更深一步說,強(qiáng)制類型轉(zhuǎn)換也是對(duì)編譯器來說,是通過編譯器編譯過程中推斷類型轉(zhuǎn)換的正誤。
??? 那么this對(duì)象是否是A *const this的值哪?首先我們先看一個(gè)樣例
?
static int iTest = 1;
class A
{
public:
???? int iData;// 簡單期間我們定義為int型
???? mutable int iData2;// mutable變量
???? int Fun1()
???? {
???????? int iTemp = 4;
???????? return ++iData;
???? };// 普通函數(shù)
???? int Fun2()const {return iData;};// 帶const的函數(shù)
};
?
int _tmain(int argc, _TCHAR* argv[])
{
???? A a;
static int iTest1 = 2;
???? a.Fun1();
static int iTest2 = 3;
system("pause");
???? return 0;
}
我們通過上面的樣例查看this的地址,我們定義static對(duì)象的目的就是為了用this指針的地址和static變量的地址進(jìn)行對(duì)照,看一看this指針究竟分配到哪里?
??? 注意我們?cè)谶@里不能直接使用&this獲得this的指針,假設(shè)我們這樣定義會(huì)提示
Error C2102 “&”要求一個(gè)L值
??? 通過上面至少我們知道,this不是一個(gè)個(gè)人定義的變量,僅僅是在執(zhí)行時(shí)刻有效。所以這時(shí)假設(shè)直接對(duì)this取地址,在編譯時(shí)刻無法通過,提示如上錯(cuò)誤。
??? 既然我們?cè)诔绦蛑袩o法通過&this取得this的地址。那么我們有什么辦法取得this的地址?我們上面已經(jīng)提到this是在執(zhí)行時(shí)刻有效,我們就以據(jù)這點(diǎn)查找this的地址。
??? 為了在取得this的地址,我們使用VC7.0下的命令窗體,在命令窗體中我們使用命令eval,通過這個(gè)命令我們能夠取得this的地址。我們還是在上面的程序中設(shè)置斷點(diǎn)
?
在debug下,我們執(zhí)行上面的程序,并進(jìn)入斷點(diǎn)后,進(jìn)行取址操作。
>eval &iTest
0x0044afa0 iTest
>eval &iTest1
0x0044afa4 iTest1
>eval &this// 注意僅僅有我們進(jìn)入Fun1()函數(shù)體內(nèi)才干取得&this的值
0x0012fdf0 "玄_"
>eval &iTest2
0x0044afa8 iTest2
?
通過對(duì)照我們能夠看出static變量iTest,iTest1,iTest2存放在全局變量區(qū)域,而&this(0x0012fdf0)的地址比&iTest(0x0044afa0)地址還要底,而static變量存放在單獨(dú)全局
區(qū)域,而且這個(gè)區(qū)域是從底地址到高地址遞增的。所以通過上面的對(duì)照至少我們能夠肯定一點(diǎn)this指針的創(chuàng)建要比static變量(或者全局變量)早。那么更比創(chuàng)建A a;對(duì)象時(shí)調(diào)用A的構(gòu)造函數(shù)早,僅僅是創(chuàng)建a對(duì)象后,this指向a對(duì)象;
當(dāng)我們創(chuàng)建兩個(gè)A類對(duì)象時(shí),會(huì)發(fā)現(xiàn)this指針的地址是同樣的,可是this指針指向?qū)ο蟛煌?。?dāng)然不同了,假設(shè)同樣。A a,b;那么a,b對(duì)象也就同樣了,這樣的方式肯定是不正確的。結(jié)論就是同一個(gè)類創(chuàng)建多個(gè)對(duì)象時(shí),多個(gè)對(duì)象的this指針是同一個(gè)指針。也就是說在單進(jìn)程單線程中this對(duì)象在放入CPU寄存器中時(shí)都是同一個(gè)地址,僅僅是指向不同的對(duì)象而已。上面的測試是在DEBUG狀態(tài)下的測試結(jié)果。
那么在Release是什么樣?要多虧VC7.0支持Release下的斷點(diǎn),我們?cè)?span lang="en-us">Release下,啟動(dòng)調(diào)試。這時(shí)須要在Release狀態(tài)下設(shè)置,優(yōu)化狀態(tài)為禁用(/Od)
>eval &this CXX0069: 錯(cuò)誤: 變量須要堆棧幀
>eval this CXX0069: 錯(cuò)誤: 變量須要堆棧幀
>eval *this CXX0069: 錯(cuò)誤: 變量須要堆棧幀
?
??? 在Release狀態(tài)下&this,this,*this不存在了,提示是變量須要堆棧幀,說明此時(shí)的this指針不存在了。難到this指針僅僅是在debug模式下有,在Release模式下沒有?而C++語言特性中并沒有說this指針在調(diào)試狀態(tài)下有而在Release模式下沒有啊?僅僅是強(qiáng)調(diào)this指針作為一種隱含參數(shù)傳遞。也就是在正確(請(qǐng)這樣理解)的程序中this應(yīng)該是不存在的,至少能夠肯定的是說在內(nèi)存中不存在this指針。
??? 我們使用C++的時(shí)候知道有一種變量定義方式,也不存放到內(nèi)存,而是直接放到寄存器中。我想你已經(jīng)猜到了就是register類型變量,以下我們測試register類型變量是否和this指針是一樣的結(jié)果。
??? 在程序中定義:register int iRegData;
??? Debug模式下
>eval iRegData
5
>eval &iRegData
0x0012fec4// 注意這個(gè)地址,看看是否和>eval &this// 注意僅僅有我們進(jìn)入Fun1()函數(shù)體內(nèi)才干取得&this的值0x0012fdf0 "玄_"在地址上非常接近啊!一個(gè)是0x0012fec4,還有一個(gè)是0x0012fdf0。
??? Release模式下
>eval iRegData
5
>eval &iRegData
0x0012fee0
通過上能夠知道在debug和Release模式下iRegData都沒有直接放入寄存器,而是在內(nèi)存中開辟了內(nèi)存空間,至于怎樣能夠在運(yùn)行時(shí)候看出register變量是放到寄存器,而不是內(nèi)存中,我還不得而知,所以哪位高人知道,麻煩告訴我一聲。看來this指針也不是register類型的,或者我如今的能力還不能確定this是register。后來才知道register對(duì)編譯器僅僅是一個(gè)提示,編譯器能夠運(yùn)行也能夠不運(yùn)行,就像inline一樣。可是至少我們能夠使用__inline宏,能夠確保函數(shù)被inline,可是register?有沒有這樣的策略,我如今還不得而知。
補(bǔ)充:定義變量類型有四中各自是
1:Auto:非static,const類型變量,比方局部變量,int i;char c等。都是auto int i;auto char c;
2:static:靜態(tài)變量,static int i,static char c;
3:const:常量變量,值不可改動(dòng)。Const int i,static char c;
4:register:內(nèi)存變量,編譯器把此值直接放入寄存器。Register int i;register char c;
上面討論我們都是從類中變量進(jìn)行討論的,可是無法確定this究竟是什么?那么我們繼續(xù)從類中的函數(shù)開始討論this。而且我們也將逐漸深入編譯狀態(tài)下。
開始的使用已經(jīng)舉了樣例,類內(nèi)函數(shù)在解釋函數(shù)時(shí),把this指針作為函數(shù)的第一個(gè)參數(shù)進(jìn)行傳遞。可是,當(dāng)高級(jí)語言被編譯成計(jì)算機(jī)能夠識(shí)別的機(jī)器碼時(shí),有一個(gè)問題就凸現(xiàn)出來:在CPU中,計(jì)算機(jī)沒有辦法知道一個(gè)函數(shù)調(diào)用須要多少個(gè)、什么樣的參數(shù),也沒有硬件能夠保存這些參數(shù)(你講看到this是一個(gè)例外)。也就是說,計(jì)算機(jī)不知道怎么給這個(gè)函數(shù)傳遞參數(shù),傳遞參數(shù)的工作必須由函數(shù)調(diào)用者和函數(shù)本身來協(xié)調(diào)。為此,計(jì)算機(jī)提供了一種被稱為棧的數(shù)據(jù)結(jié)構(gòu)來支持參數(shù)傳遞。
??? 棧是一種先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),棧有一個(gè)存儲(chǔ)區(qū)、一個(gè)棧頂指針。棧頂指針指向堆棧中第一個(gè)可用的數(shù)據(jù)項(xiàng)(被稱為棧頂)。用戶能夠在棧頂上方向棧中增加數(shù)據(jù),這個(gè)操作
被稱為壓棧(Push),壓棧以后,棧頂自己主動(dòng)變成新增加數(shù)據(jù)項(xiàng)的位置,棧頂指針也隨之修
改。用戶也能夠從堆棧中取走棧頂,稱為彈出棧(pop),彈出棧后,棧頂下的一個(gè)元素變
成棧頂,棧頂指針隨之改動(dòng)。
函數(shù)調(diào)用時(shí),調(diào)用者依次把參數(shù)壓棧,然后調(diào)用函數(shù),函數(shù)被調(diào)用以后,在堆棧中取得數(shù)據(jù),并進(jìn)行計(jì)算。函數(shù)計(jì)算結(jié)束以后,或者調(diào)用者、或者函數(shù)本身改動(dòng)堆棧,使堆?;謴?fù)原裝。在參數(shù)傳遞中,有兩個(gè)非常重要的問題必須得到明白說明:當(dāng)參數(shù)個(gè)數(shù)多于一個(gè)時(shí),依照什么順序把參數(shù)壓入堆棧函數(shù)調(diào)用后,由誰來把堆棧恢復(fù)原裝在高級(jí)語言中,通過函數(shù)調(diào)用約定來說明這兩個(gè)問題。常見的調(diào)用約定有:
stdcall
cdecl
fastcall
thiscall
naked call
原來函數(shù)調(diào)用約定也有這么多啊,看這都有點(diǎn)暈了呵呵。由于這篇文章講的是this指針,所以在這里我們主要討論thiscall。
?????? thiscall是唯一一個(gè)不能明白指明的函數(shù)修飾,由于thiscall不是keyword(所以不要在C++keyword中找了)。它是C++類成員函數(shù)缺省的調(diào)用約定。由于成員函數(shù)調(diào)用有一個(gè)this指針,因此必須特殊處理,thiscall意味著:參數(shù)從右向左入棧,假設(shè)參數(shù)個(gè)數(shù)確定,this指針通過ecx傳遞給被調(diào)用者;假設(shè)參數(shù)個(gè)數(shù)不確定,this指針在全部參數(shù)壓棧后被壓入堆棧。對(duì)參數(shù)個(gè)數(shù)不定的,調(diào)用者清理堆棧,否則函數(shù)自己清理堆棧為了說明這個(gè)調(diào)用約定,定義例如以下類和使用代碼:
class A
{
public:
int function1(int a,int b);
int function2(int a,...);// 定義VA(可變)函數(shù)
};
int A::function1 (int a,int b)
{
return a+b;
}
int A::function2(int a,...)
{
va_list ap;
va_start(ap,a);
int i;
int result = 0;
for(i = 0 i < a i ++)
{
result += va_arg(ap,int);
}
return result;
}
void callee()
{
A a;
a.function1 (1,2);
a.function2(3,1,2,3);
}
callee函數(shù)被翻譯成匯編后就變成:
//函數(shù)function1調(diào)用
0401C1D push 2
00401C1F push 1
00401C21 lea ecx,[ebp-8]
00401C24 call function1 // 注意,這里this沒有被入棧,而是通過ECX傳遞this指針
此時(shí)寄存器的各值例如以下
EAX = 00000003 EBX = 7FFDF000 ECX = 0012EE43
EDX = 00000001 ESI = 00000000 EDI = 0012EE48
EIP = 0041707A ESP = 0012ED70 EBP = 0012EE48
EFL = 00000206
察看this指針
>eval this
0x0012ee43// 看看這個(gè)值是否和ECX同樣
//函數(shù)function2調(diào)用
00401C29 push 3
00401C2B push 2
00401C2D push 1
00401C2F push 3
00401C31 lea eax,[ebp-8] // 這里引入this指針,并把this指針放入棧內(nèi)
EAX = 00000006 EBX = 7FFDF000 ECX = 0012ED70
EDX = 00000006 ESI = 00000000 EDI = 0012EE48
EIP = 0041708E ESP = 0012ED70 EBP = 0012EE48
EFL = 00000212
察看this指針
>eval this
0x0012ee43// 看看這個(gè)值是否和ECX同樣
00401C34 push eax
00401C35 call function2
00401C3A add esp,14h
?
到如今,我們對(duì)this得了解還說不上深入了解。簡單得說this就是指向?qū)ο笞陨淼囊粋€(gè)指針,討論這么多事實(shí)上就是想了解this在反編譯階段是怎樣傳遞執(zhí)行得。或許就this的了解我們就能夠基于以上討論已經(jīng)足夠了??墒?/span>this的應(yīng)用并不簡單的就是這些內(nèi)容,比方在ATL中,就有專門函數(shù)用來保存回復(fù)this指針的策略;我們?cè)谥剌doperator=也須要通過this推斷賦值等號(hào)兩邊對(duì)象,是否指向同一個(gè)對(duì)象。
?
關(guān)于指針:指針和其他變量(int,char等)一樣,在聲明后會(huì)在內(nèi)存中申請(qǐng)內(nèi)存空間,存儲(chǔ)在在程序的堆棧上,大小一般都是一個(gè)機(jī)器字的長度(比方在32位機(jī)上是4個(gè)字節(jié))。簡單的說指針是指向內(nèi)存中地址的變量,能夠是數(shù)據(jù)的地址也能夠是函數(shù)的地址。一句話:指針是一種用于儲(chǔ)存“另外一個(gè)變量的地址”的變量?;蛘卟鸪蓛删?#xff1a;指針是一個(gè)變量,它的值是另外一個(gè)變量的地址。
?
參考資料
孫曉濤等《Windows高級(jí)編程》西北工業(yè)大學(xué)出版社(1997年10月 西安)
逸學(xué)堂《關(guān)于this指針的深入探討》CSDN
《C++編程思想》
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/mfrbuaa/p/4204169.html
總結(jié)
以上是生活随笔為你收集整理的深入探讨this指针的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ios 图片 相册 存储方式
- 下一篇: 【MFC】BitBlt详解