クラスは非常に単純にメモリに格納されます-構造体とほぼ同じ方法です。クラスインスタンスが格納されている場所のメモリを調べると、そのフィールドが単純に次々にパックされていることがわかります。
ただし、クラスに仮想メソッドがある場合は違いがあります。このような場合、クラスインスタンスに最初に格納されるのは、仮想メソッドテーブルへのポインタです。これにより、仮想メソッドが正しく機能します。これについてはインターネットでもっと読むことができます。これはもう少し高度なトピックです。幸いなことに、それについて心配する必要はありません。コンパイラがすべてを自動的に実行します(つまり、VMTを処理し、心配する必要はありません)。
メソッドに行きましょう。あなたが見るとき:
void MyClass::myFunc(int i, int j) { }
実際、コンパイラはそれを次のようなものに変換します。
void myFunc(MyClass * this, int i, int j) { }
そして、あなたが電話するとき:
myClassInstance->myFunc(1, 2);
コンパイラは次のコードを生成します。
myFunc(myClassInstance, 1, 2);
これは単純化であることに注意してください。これよりも少し複雑な場合もありますが(特に仮想メソッド呼び出しについて説明する場合)、コンパイラによるクラスの処理方法が多かれ少なかれ示されています。WinDbgなどの低レベルのデバッガーを使用する場合は、メソッド呼び出しのパラメーターを調べることができます。最初のパラメーターは通常、メソッドを呼び出したクラスインスタンスへのポインターであることがわかります。
現在、同じタイプのすべてのクラスは、メソッドのバイナリ(コンパイルされたコード)を共有しています。したがって、クラスインスタンスごとにそれらのコピーを作成する意味はありません。したがって、メモリに保持されるコピーは1つだけであり、すべてのインスタンスがそれを使用します。クラスのインスタンスがない場合でも、なぜメソッドへのポインタを取得できるのかが明確になっているはずです。
ただし、変数に保持されているメソッドを呼び出す場合は、常にクラスインスタンスを提供する必要があります。これは、非表示の「this」パラメーターで渡すことができます。
編集:コメントに応えて
ポインタメンバーの詳細については、別のSOの質問を参照してください。メンバーへのポインタは、クラスインスタンスの先頭と指定されたフィールドの違いを格納していると思います。メンバーへのポインターを使用してフィールドの値を取得しようとすると、コンパイラーはクラス・インスタンスの先頭を見つけ、メンバーへのポインターに格納されているバイト数だけ移動して、指定されたフィールドに到達します。
各クラスインスタンスには、非静的フィールドの独自のコピーがあります。そうでない場合、それらはあまり役に立ちません。
メソッドへのポインターと同様に、メンバーへのポインターを直接使用することはできません。ここでも、クラスインスタンスを提供する必要があります。
私が言うことの証拠は正しいので、ここにあります:
class C
{
public:
int a;
int b;
};
// Disassembly of fragment of code:
int C::*pointerToA = &C::a;
00DB438C mov dword ptr [pointerToA],0
int C::*pointerToB = &C::b;
00DB4393 mov dword ptr [pointerToB],4
pointerToAとpointerToBに格納されている値を確認できますか?フィールドa
はクラスインスタンスの先頭から0バイト離れているため、値0はpointerToAに格納されます。一方、フィールドは4バイト長b
のフィールドの後に格納a
されるため、値4はpointerToBに格納されます。