[C++]クラスの空のポインタがメンバー関数を呼び出すと、何が起こりますか?

4919 ワード

クラスのインスタンスがメンバー関数を呼び出す原理
実際には、オブジェクトインスタンスまたはポインタインスタンス呼び出しにかかわらず、最下位呼び出しのプロセスは同じであり、現在のオブジェクトのポインタを呼び出したメンバー関数にパラメータとして渡します.次の関連インスタンスコードで検証します.
実験のC++コード
class Student
{
private:
	int age;
public:
	Student() {}
	Student(int age) : age(age) {}
	int getAge() { return this->age; }
};

int main(int argc, char const *argv[])
{
	Student s(10);
	int age = s.getAge();

	Student* ps = new Student(10);
	age = ps->getAge();

	return 0;
}

VS 2015デバッグ機能に基づく逆アセンブリコード
int main(int argc, char const *argv[])
{
00A41860  push        ebp  
00A41861  mov         ebp,esp  
00A41863  push        0FFFFFFFFh  
00A41865  push        0A461D2h  
00A4186A  mov         eax,dword ptr fs:[00000000h]  
00A41870  push        eax  
00A41871  sub         esp,104h  
00A41877  push        ebx  
00A41878  push        esi  
00A41879  push        edi  
00A4187A  lea         edi,[ebp-110h]  
00A41880  mov         ecx,41h  
00A41885  mov         eax,0CCCCCCCCh  
00A4188A  rep stos    dword ptr es:[edi]  
00A4188C  mov         eax,dword ptr [__security_cookie (0A4B004h)]  
00A41891  xor         eax,ebp  
00A41893  mov         dword ptr [ebp-10h],eax  
00A41896  push        eax  
00A41897  lea         eax,[ebp-0Ch]  
00A4189A  mov         dword ptr fs:[00000000h],eax  
	Student s(10);
00A418A0  push        0Ah  /*         : 10 */
00A418A2  lea         ecx,[s]  /*   s     ,       ECX*/
00A418A5  call        Student::Student (0A4103Ch)  /*        0A4103Ch     */
	int age = s.getAge();
00A418AA  lea         ecx,[s]  /*   s     ,       ECX*/
00A418AD  call        Student::getAge (0A412D5h)  /*        0A412D5h     */
00A418B2  mov         dword ptr [age],eax  /*            age*/

	Student* ps = new Student(10);
00A418B5  push        4  /*          ,   int    ,  4  (32   )*/
00A418B7  call        operator new (0A412A3h)  /*           ,  C malloc  */
00A418BC  add         esp,4  /*   4   ,    operator new            */
00A418BF  mov         dword ptr [ebp-108h],eax  /*eax           ,   ptr [ebp-108h]         */
00A418C5  mov         dword ptr [ebp-4],0  
00A418CC  cmp         dword ptr [ebp-108h],0  
00A418D3  je          main+8Ah (0A418EAh)  /*      ,        0,   ,   0A418EAh*/
00A418D5  push        0Ah  /*          : 10*/
00A418D7  mov         ecx,dword ptr [ebp-108h]  /*   ps                  ECX*/
00A418DD  call        Student::Student (0A4103Ch)  /*         0A4103Ch      */
00A418E2  mov         dword ptr [ebp-110h],eax  
00A418E8  jmp         main+94h (0A418F4h)  
00A418EA  mov         dword ptr [ebp-110h],0  
00A418F4  mov         eax,dword ptr [ebp-110h]  
00A418FA  mov         dword ptr [ebp-0FCh],eax  
00A41900  mov         dword ptr [ebp-4],0FFFFFFFFh  
00A41907  mov         ecx,dword ptr [ebp-0FCh]  
00A4190D  mov         dword ptr [ps],ecx  
	age = ps->getAge();
00A41910  mov         ecx,dword ptr [ps]  /*   s             ECX*/
00A41913  call        Student::getAge (0A412D5h)  /*         0A412D5h      */
	age = ps->getAge();
00A41918  mov         dword ptr [age],eax  

	return 0;
00A4191B  xor         eax,eax  
}

ぶんせき
ソースコードとアセンブリコードの比較から、メンバー関数とクラスのインスタンスはバインド関係がなく、メンバー関数はクラスに属し、メモリには有効なメモリアドレスのみであることがわかります.メンバー関数でインスタンスを使用する必要がある場合は、レジスタECXを介して渡されます.
ここで考えさせられますが、なぜスタックを通じて伝わらないのでしょうか.
まず、スタックはメモリにあり、レジスタはCPUにあり、両者の読み書き速度は天差地別であり、効率の最適化であることを理解しなければならない.また,現在のメンバ関数にあることを約束した場合,ECXレジスタを修正しないとよい.
しかし、スタックがダイナミックなメモリ領域であるため、スタックを使用して転送される場合、EBPとESPを使用してメンテナンスしても、現在のインスタンスのアドレスの追跡とメンテナンスが容易ではありません.オフセット量を加える必要があり、読み書き効率に負担がかかります.メンテナンスがうまくいかないと、データが錯乱します.(ここはすべて個人分析で、間違いがあれば指摘することができます)
 
回帰ヘッダー
したがって、タイトルに戻り、クラスの空のポインタがメンバー関数を呼び出した場合、コンパイルは通過します.次に、実行中に次の2つのケースに分けます.
一、呼び出されたメンバー関数でthisポインタを使用して現在のインスタンスのメンバー変数を呼び出さず、プログラムが正常に実行されてもエラーは報告されない.
class Student
{
private:
	int age;
public:
	Student() {}
	Student(int age) : age(age) {}
	void eat() {}
};

int main(int argc, char const *argv[])
{
	Student* ps = nullptr;
	ps->eat();
	return 0;
}

二、呼び出されたメンバー関数でthisポインタを使用して現在のインスタンスのメンバー変数を読み書きする場合、まず呼び出されたメンバー関数は正常に呼び出され、コードの実行はメンバー関数の領空に入ったが、コードが現在のインスタンスを読み書きするメンバー変数に実行されると、メモリアクセス異常が報告される.
最後に発散して考える
メンバー関数はクラスに属し、インスタンスに属していないので、クラスの静的関数もクラスに属しているのではないでしょうか.クラスの空のポインタで静的関数を呼び出すと、何が起こるのでしょうか.上のコード:
#include 
using namespace std;

class Student
{
private:
	int age;
public:
	Student() {}
	Student(int age) : age(age) {}
	static int classtime() {
		return 9;
	}
	void eat() {}
};

int main(int argc, char const *argv[])
{
	Student* ps = nullptr;
	int time = ps->classtime();
	cout << time << endl;
	return 0;
}

実験を経て、このコードは正常に実行できることが分かった.実は原理の上ですべて言って、ここでむだに述べません.主な点は、クラスの静的関数がインスタンスのメンバーデータに動作しないため、この呼び出しは安全です.しかし、実際のプログラミングでは、この方法は使われないでしょう(個人的には、クラス名で呼び出すのはおいしくないのか、クラスポインタを書いてから呼び出すのか)
 
まとめ
面接のときに聞かれて、自分はできますが、はっきり答えられないのも罪なので、ここでまとめてみましょう!!