C++のthisポインタについて

3592 ワード

/**********************************************************************  
 * Copyright (c)2015,WK Studios
 * Filename:   main.cpp 
 * Compiler: GCC,VS,VC6.0  win32  
 * Author:WK  
 * Time: 2015 4 7
 ************************************************************************/ 
#include<iostream>
using namespace std;
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; 
}
int main()
{

CNullPointCall *pNull = NULL;//   ,         
pNull->Test1();     // call 1
pNull->Test2();     // call 2
pNull->Test3(13);  // call 3
pNull->Test4();   // call 4


return 0;
}

    
私がどうしてそんなことを聞いたのか不思議に思うに違いない.NULLの値を持つポインタがクラスのメンバー関数を呼び出すのにどのように使用できますか?!しかし、実際には驚くべきことに、call 4の行コードを除いて、残りの3つのクラスメンバー関数の呼び出しは成功し、結果を正確に出力することができ、この3行コードを含むプログラムは非常によく実行することができます.注意深く比較した結果,call 4の行コードは他の3行コードと本質的に異なることが分かった:クラスCNullPointCallのメンバー関数にthisポインタが用いられた.クラスメンバー関数では、オブジェクトが個別のメンバー関数体に対応するのではなく、このようなすべてのオブジェクトがこのメンバー関数体を共有します.プログラムがコンパイルされると、このメンバー関数のアドレスが決定されます.メンバー関数がこのような各オブジェクトに属するデータを区別できるのは,このthisポインタによる.関数内のクラス・データ・メンバーへのすべてのアクセスは、this->データ・メンバーの方法に変換されます.オブジェクトのthisポインタはオブジェクト自体の一部ではありませんsizeofには影響しません(「オブジェクト」)の結果です.この役割ドメインはクラス内部にあり、クラスの非静的メンバー関数でクラスの非静的メンバーにアクセスすると、コンパイラはオブジェクト自体のアドレスを自動的に隠しパラメータとして関数に渡します.すなわち、thisポインタを書かなくても、コンパイラはコンパイル時にthisを加え、非静的メンバー関数の隠しパラメータとして、各メンバーへのアクセスはthisによって行われます.
上記の例では、thisの値、すなわちpNullの値である.つまりthisの値はNULLです.Test 1()は静的関数であり、コンパイラはthisポインタを渡さないので、call 1の行コードは正しく呼び出すことができます(ここではCNullPointCall::Test 1()に相当します).Test 2()とTest 3()の2つのメンバー関数の場合、コンパイラはこの2つの関数にthisポインタを渡しますが、クラスのメンバー変数にthisポインタでアクセスしていないため、call 2とcall 3の2行のコードを正しく呼び出すことができます.メンバー関数Test 4()に対してクラスにアクセスするメンバー変数であるため、thisポインタを使用します.このとき、thisポインタの値がNULLであることが判明すると、プログラムがクラッシュします.
実際には、コンパイラがTest 4()とcall 4を次のような形式に変換することが想像できます.
void CNullPointCall::Test4( CNullPointCall*this )
{
    cout << this->m_iTest << endl; 
}




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) 
静的関数Test 1()と他の3つの非静的関数呼び出しを比較することによって生成されたアセンブリコードは、非静的関数呼び出しの前にオブジェクトを指すポインタpNull(すなわちthisポインタ)がecxレジスタに格納されることを示している(mov ecx,dword ptr[pNull]).これがthisポインタの特殊な点です.call 3の行のC++コードのアセンブリコードを見て、thisポインタと一般的な関数パラメータの違いを見ることができます:一般的な関数パラメータは直接スタックに押し込まれます(push 0 Dh)、thisポインタはecxレジスタに格納されます.クラスの非メンバー関数でクラスのメンバー変数を使用する場合は、ecxレジスタにアクセスしてオブジェクトを指すthisポインタを取得し、thisポインタにメンバー変数のオフセット量を加えて対応するメンバー変数を見つけることができます.