this指標可以這樣理解

2021-05-27 22:18:32 字數 3831 閱讀 1424

有下面的乙個簡單的類: class cnullpointcall

;int cnullpointcall::m_istatic = 0;

void cnullpointcall::test1()

void cnullpointcall::test2()

void cnullpointcall::test3(int itest)

void cnullpointcall::test4()

那麼下面的**都正確嗎?都會輸出什麼?

cnullpointcall *pnull = null; // 沒錯,就是給指標賦值為空

pnull->test1(); // call 1

pnull->test2(); // call 2

pnull->test3(13); // call 3

pnull->test4(); // call 4

你肯定會很奇怪我為什麼這麼問。乙個值為null的指標怎麼可以用來呼叫類的成員函式呢?!可是實事卻很讓人吃驚:除了call 4那行**以外,其餘3個類成員函式的呼叫都是成功的,都能正確的輸出結果,而且包含這3行**的程式能非常好的執行。

經過細心的比較就可以發現,call 4那行**跟其他3行**的本質區別:類cnullpointcall的成員函式中用到了this指標。

對於類成員函式而言,並不是乙個物件對應乙個單獨的成員函式體,而是此類的所有物件共用這個成員函式體。 當程式被編譯之後,此成員函式位址即已確定。而成員函式之所以能把屬於此類的各個物件的資料區別開, 就是靠這個this指標。函式體內所有對類資料成員的訪問, 都會被轉化為this->資料成員的方式。

而乙個物件的this指標並不是物件本身的一部分,不會影響sizeof(「物件」)的結果。this作用域是在類內部,當在類的非靜態成員函式中訪問類的非靜態成員的時候,編譯器會自動將物件本身的位址作為乙個隱含引數傳遞給函式。也就是說,即使你沒有寫上this指標,編譯器在編譯的時候也是加上this的,它作為非靜態成員函式的隱含形參,對各成員的訪問均通過this進行。

對於上面的例子來說,this的值也就是pnull的值。也就是說this的值為null。而test1()是靜態函式,編譯器不會給它傳遞this指標,所以call 1那行**可以正確呼叫(這裡相當於cnullpointcall::test1());對於test2()和test3()兩個成員函式,雖然編譯器會給這兩個函式傳遞this指標,但是它們並沒有通過this指標來訪問類的成員變數,因此call 2和call 3兩行**可以正確呼叫;而對於成員函式test4()要訪問類的成員變數,因此要使用this指標,這個時候發現this指標的值為null,就會造成程式的崩潰。   

其實,我們可以想象編譯器把test4()轉換成如下的形式:

void cnullpointcall::test4(cnullpointcall *this)

而把call 4那行**轉換成了下面的形式:

cnullpointcall::test4(pnull);

所以會在通過this指標訪問m_itest的時候造成程式的崩潰。

下面通過檢視上面**用vc 2005編譯後的彙編**來詳細解釋一下神奇的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)

通過比較靜態函式test1()和其他3個非靜態函式呼叫所生成的的彙編**可以看出:非靜態函式呼叫之前都會把指向物件的指標pnull(也就是this指標)放到ecx暫存器中(mov ecx,dword ptr [pnull])。這就是this指標的特殊之處。看call 3那行c++**的彙編**就可以看到this指標跟一般的函式引數的區別:一般的函式引數是直接壓入棧中(push 0dh),而this指標卻被放到了ecx暫存器中。在類的非成員函式中如果要用到類的成員變數,就可以通過訪問ecx暫存器來得到指向物件的this指標,然後再通過this指標加上成員變數的偏移量來找到相應的成員變數。

下面再通過另外乙個例子來說明this指標是怎樣被傳遞到成員函式中和如何使用this來訪問成員變數的。

依然是乙個很簡單的類:

class ctest

;void ctest::setvalue()

用如下的**呼叫成員函式:

ctest test;

test.setvalue();

上面的c++**的彙編**為:

ctest test;

test.setvalue();

004117dc lea         ecx,[test]

004117df call        ctest::setvalue (4111cch)

同樣的,首先把指向物件的指標放到ecx暫存器中;然後呼叫類ctest的成員函式setvalue()。位址4111cch那裡存放的其實就是乙個轉跳指令,轉跳到成員函式setvalue()內部。

004111cc jmp         ctest::setvalue (411750h)

而411750h才是類ctest的成員函式setvalue()的位址。

void ctest::setvalue()

00411786 pop         edi

00411787 pop         esi

00411788 pop         ebx

00411789 mov         esp,ebp

0041178b pop         ebp

0041178c ret

下面對上面的彙編**中的重點行進行分析:

1、將ecx暫存器中的值壓棧,也就是把this指標壓棧。

2、ecx暫存器出棧,也就是this指標出棧。

3、將ecx的值放到指定的地方,也就是this指標放到[ebp-8]內。

4、取this指標的值放入eax暫存器內。此時,this指標指向test物件,test物件只有兩個int型的成員變數,在test物件記憶體中連續存放,也就是說this指標目前指向m_ivalue1。

5、給暫存器eax指向的位址賦值0dh(十六進製制的13)。其實就是給成員變數m_ivalue1賦值13。

6、同4。

7、給暫存器eax指向的位址加4的位址賦值。在4中已經說明,eax暫存器內存放的是this指標,而this指標指向連續存放的int型的成員變數m_ivalue1。this指標加4(sizeof(int))也就是成員變數m_ivalue2的位址。因此這一行就是給成員變數m_ivalue2賦值。

通過上面的分析,我們可以從底層了解了c++中this指標的實現方法。雖然不同的編譯器會使用不同的處理方法,但是c++編譯器必須遵守c++標準,因此對於this指標的實現應該都是差不多的。

原來可以這樣

葉子有沒有腳?顯然沒有.可是卻可以飛遍天涯海角,真好.樹兒有沒有眼睛?顯然沒有.可是卻可以仰望天空,真好.岩石有沒有呼吸?顯然沒有.可是卻可以感受陽光,真好 草原有沒有笑臉?顯然沒有.可是卻充滿了收穫的喜悅,真好 人們有沒有魔幻的法力?顯然沒有.可是卻有美妙的夢境,真好.烏雲有沒有眼淚?顯然沒有.可...

ARP欺騙攻擊原理也可以這樣理解

中子明給大家呈現了arp一攻一防的兩個例項,在網上有很多文章描述了arp欺騙攻擊的原理,但還是有很多51cto反映,有很多不理解和不明白的地方,所以本文子明特別用通俗的例子,說明arp欺騙攻擊的原理,使arp欺騙攻擊原理更加清楚的展現在你的面前。了解arp位址解析協議 我們先來簡單描述下什麼是arp...

可以這樣去理解group by和聚合函式

寫在前面的話 用了好久group by,今天早上一覺醒來,突然感覺group by好陌生,總有個筋別不過來,為什麼不能夠select from table group by id,為什麼一定不能是 而是某乙個列或者某個列的聚合函式,group by 多個字段可以怎麼去很好的理解呢?不過最後還是轉過來...