[C++]クラスの空のポインタがメンバー関数を呼び出すと、何が起こりますか?
4919 ワード
クラスのインスタンスがメンバー関数を呼び出す原理
実際には、オブジェクトインスタンスまたはポインタインスタンス呼び出しにかかわらず、最下位呼び出しのプロセスは同じであり、現在のオブジェクトのポインタを呼び出したメンバー関数にパラメータとして渡します.次の関連インスタンスコードで検証します.
実験のC++コード
VS 2015デバッグ機能に基づく逆アセンブリコード
ぶんせき
ソースコードとアセンブリコードの比較から、メンバー関数とクラスのインスタンスはバインド関係がなく、メンバー関数はクラスに属し、メモリには有効なメモリアドレスのみであることがわかります.メンバー関数でインスタンスを使用する必要がある場合は、レジスタECXを介して渡されます.
ここで考えさせられますが、なぜスタックを通じて伝わらないのでしょうか.
まず、スタックはメモリにあり、レジスタはCPUにあり、両者の読み書き速度は天差地別であり、効率の最適化であることを理解しなければならない.また,現在のメンバ関数にあることを約束した場合,ECXレジスタを修正しないとよい.
しかし、スタックがダイナミックなメモリ領域であるため、スタックを使用して転送される場合、EBPとESPを使用してメンテナンスしても、現在のインスタンスのアドレスの追跡とメンテナンスが容易ではありません.オフセット量を加える必要があり、読み書き効率に負担がかかります.メンテナンスがうまくいかないと、データが錯乱します.(ここはすべて個人分析で、間違いがあれば指摘することができます)
回帰ヘッダー
したがって、タイトルに戻り、クラスの空のポインタがメンバー関数を呼び出した場合、コンパイルは通過します.次に、実行中に次の2つのケースに分けます.
一、呼び出されたメンバー関数でthisポインタを使用して現在のインスタンスのメンバー変数を呼び出さず、プログラムが正常に実行されてもエラーは報告されない.
二、呼び出されたメンバー関数でthisポインタを使用して現在のインスタンスのメンバー変数を読み書きする場合、まず呼び出されたメンバー関数は正常に呼び出され、コードの実行はメンバー関数の領空に入ったが、コードが現在のインスタンスを読み書きするメンバー変数に実行されると、メモリアクセス異常が報告される.
最後に発散して考える
メンバー関数はクラスに属し、インスタンスに属していないので、クラスの静的関数もクラスに属しているのではないでしょうか.クラスの空のポインタで静的関数を呼び出すと、何が起こるのでしょうか.上のコード:
実験を経て、このコードは正常に実行できることが分かった.実は原理の上ですべて言って、ここでむだに述べません.主な点は、クラスの静的関数がインスタンスのメンバーデータに動作しないため、この呼び出しは安全です.しかし、実際のプログラミングでは、この方法は使われないでしょう(個人的には、クラス名で呼び出すのはおいしくないのか、クラスポインタを書いてから呼び出すのか)
まとめ
面接のときに聞かれて、自分はできますが、はっきり答えられないのも罪なので、ここでまとめてみましょう!!
実際には、オブジェクトインスタンスまたはポインタインスタンス呼び出しにかかわらず、最下位呼び出しのプロセスは同じであり、現在のオブジェクトのポインタを呼び出したメンバー関数にパラメータとして渡します.次の関連インスタンスコードで検証します.
実験の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;
}
実験を経て、このコードは正常に実行できることが分かった.実は原理の上ですべて言って、ここでむだに述べません.主な点は、クラスの静的関数がインスタンスのメンバーデータに動作しないため、この呼び出しは安全です.しかし、実際のプログラミングでは、この方法は使われないでしょう(個人的には、クラス名で呼び出すのはおいしくないのか、クラスポインタを書いてから呼び出すのか)
まとめ
面接のときに聞かれて、自分はできますが、はっきり答えられないのも罪なので、ここでまとめてみましょう!!