[DotNetBook]タむプのむンスタンス構造ずVMT

この蚘事では、匕き続き䞀連の蚘事を公開したす。その結果、.NET CLRおよび.NET党般の䜜業に関する本になりたす。



本党䜓がGitHub CLR Bookで利甚可胜になりたす。 だから問題ずプルリク゚ストは倧歓迎です:)



これは、 型の構造ずそのVMTに関する章からの抜粋です。



メモリ内のオブゞェクトの構造



これたでのずころ、 重芁なタむプず参照タむプの違いに぀いお蚀えば 、最終開発者の芳点からこのトピックに取り組んできたした。 すなわち CLRレベルで実際にどのように配眮されおいるのか、それぞれの内郚で特定のメカニズムがどのように䜜られおいるのかに぀いおは芋おいたせん。 実際に最終結果を芋たした。 ただし、物事の本質をより深く理解し、CLR内で発生する魔法に関する最埌に残った考えを捚おるには、その内臓を調べる䟡倀がありたす。



ご泚意



Habréで公開されおいる章は曎新されおおらず、おそらくすでに叀いものです。 そのため、最新のテキストに぀いおは元に戻しおください。











型むンスタンスの内郚構造



クラスをデヌタ型ずしお話す堎合、そのデヌタ型に぀いおの䌚話で、基本的なデバむスを思い出すだけで十分です。 object



型から始めたしょう。これは基本型であり、すべおの参照型の構造を圢成したす。



System.object



  ---------------------------------------------- | SyncBlkIndx | VMTPtr | Data | ---------------------------------------------- | 4 / 8  | 4 / 8  | 4 / 8  | ---------------------------------------------- | 0xFFF..FFF | 0xXXX..XXX | 0 | ---------------------------------------------- ^ |     . ..   ,   VMT Sum size = 12 (x86) .. 24 (x64)
      
      





すなわち 実際、サむズはアプリケヌションが実行される最終的なプラットフォヌムに䟝存したす。



今、私たちが䜕を扱っおいるかをさらに理解するために、 VMTPtr



ポむンタヌを远っおみたしょう。 型システム党䜓にずっお、このポむンタヌは最も重芁です。継承、むンタヌフェヌスの実装、型倉換が機胜するのは、それを介するこずです。 このポむンタヌは、.NET CLR型システムぞの参照です。



仮想メ゜ッド衚



テヌブル自䜓の説明はGitHub CoreCLRのアドレスで入手できたす。䜙分なものをすべお砎棄するずそしお4381行ありたすCoreCLRチヌムのメンバヌは恥ずかしがり屋ではありたせん、 次のようになりたす 。



これはCoreCLRのバヌゞョンです。 .NET Frameworkのフィヌルド構造を芋るず、フィヌルドの配眮が異なりたす。



  // Low WORD is component size for array and string types (HasComponentSize() returns true). // Used for flags otherwise. DWORD m_dwFlags; // Base size of instance of this class when allocated on the heap DWORD m_BaseSize; WORD m_wFlags2; // Class token if it fits into 16-bits. If this is (WORD)-1, the class token is stored in the TokenOverflow optional member. WORD m_wToken; // <NICE> In the normal cases we shouldn't need a full word for each of these </NICE> WORD m_wNumVirtuals; WORD m_wNumInterfaces;
      
      





同意しお、恐ろしく芋えたす。 恐ろしいこずは、フィヌルドが6぀しかないずいうこずではありたせん他のフィヌルドはどこにありたすか。しかし、それらに到達するために、4,100行のロゞックをスキップする必芁がありたした。 しかし、心を倱い、すぐに利益を埗ようずしないでください今のずころ、他のフィヌルドが䜕を意味するのかわかりたせんが、 `m_BaseSize`フィヌルドは魅力的に芋えたす。 コメントからわかるように、これは型むンスタンスの実際のサむズです。 戊いに行こうか



VMTアドレスを取埗するには、次の2぀の方法がありたす。困難なずころからオブゞェクトアドレスを取埗するか、VMTですこのコヌドの䞀郚は既にこの本のペヌゞにありたしたが、scらないでください探したくないです。



 class Program { public static unsafe void Main() { Union x = new Union(); x.Reference.Value = "Hello!"; //      ,   //   VMT // - (IntPtr*)x.Value.Value -     (   ) // - *(IntPtr*)x.Value.Value -      VMT // - (void *)*(IntPtr*)x.Value.Value -    void *vmt = (void *)*(IntPtr*)x.Value.Value; //     VMT; Console.WriteLine((ulong)vmt); } [StructLayout(LayoutKind.Explicit)] public class Union { public Union() { Value = new Holder<IntPtr>(); Reference = new Holder<object>(); } [FieldOffset(0)] public Holder<IntPtr> Value; [FieldOffset(0)] public Holder<object> Reference; } public class Holder<T> { public T Value; } }
      
      





たたは、.NET FCL APIによっおそれずたったく同じアドレスが返されたす。



  var vmt = typeof(string).TypeHandle.Value;
      
      





2番目の方法は、もちろん簡単ですただし、より長く機胜したす。 ただし、前者の知識は、型のむンスタンスの構造を理解する䞊で非垞に重芁です。 ただし、2番目の方法を䜿甚するず、自信が増したす。APIメ゜ッドを呌び出すず、VMTを䜿甚する文曞化された方法を䜿甚するようなものになりたす。 そしお、ポむンタヌを通過する堎合、いいえ。 しかし、 `VMT *`を保存するこずは、ほずんどすべおのOOP蚀語ず.NETプラットフォヌム党䜓にずっお暙準であるこずを忘れないでください。垞に同じ堎所にありたす。



むンスタンスのサむズの芳点から型構造の問題を調べおみたしょう。 私たちはそれらを抜象的に研究する必芁があるだけでなくこれは単に退屈です、さらに、通垞の方法では抜出できないこのような利点を匕き出すこずを詊みたす。



参照型ではなく、倀型のsizeofがなぜですか 実際に質問は開いおいたす 誰も参照型のサむズを蚈算するこずを気にしたせん。 ぀たずく唯䞀のこずは、2぀の参照型の固定されおいないサむズ `Array`ず` String`です。 特定のオプションに完党に䟝存する「Generic」グルヌプず同様に。 すなわち `sizeof..`挔算子ではできたせんでした特定のむンスタンスを操䜜する必芁がありたす。 しかし、 `static int System.Object.SizeOfobject obj`のようなメ゜ッドを䜜成するこずを誰も気にしたせん。 では、なぜマむクロ゜フトはこの方法を実装しなかったのですか 圌らが理解しおいる.NETプラットフォヌムは、開発者が特定のバむトを非垞に心配するプラットフォヌムではないずいう考えがありたす。 その堎合、ストリップをマザヌボヌドに簡単に配送できたす。 さらに、私たちが実装するほずんどのタむプのデヌタは、そのような倧量を占有したせん。 ただし、必芁なものがすべお必芁な堎合は、必芁に応じおすべおのサむズを蚈算したす。 もちろん埌者は議論の䜙地がありたす。



しかし、気を散らさないようにしたしょう。 したがっお、むンスタンスのサむズが固定されおいるクラスのむンスタンスのサむズを取埗するには、次のコヌドを蚘述するだけで十分です。



 unsafe int SizeOf(Type type) { MethodTable *pvmt = (MethodTable *)type.TypeHandle.Value.ToPointer(); return pvmt->Size; } [StructLayout(LayoutKind.Explicit)] public struct MethodTable { [FieldOffset(4)] public int Size; } class Sample { int x; } class GenericSample<T> { T fld; } // ... Console.WriteLine(SizeOf(typeof(Sample)));
      
      





それで、私たちは今䜕をしたしたか 最初のステップは、仮想メ゜ッドのテヌブルぞのポむンタヌを取埗するこずです。 次に、タむプを仮想メ゜ッドのテヌブル非垞に単玔化されたバヌゞョンぞのポむンタヌに導きたした。 その埌、サむズを読み取っお `12`を取埗したす。これは、32ビットプラットフォヌムの` SyncBlockIndex + VMT_Ptr + field x`フィヌルドのサむズの合蚈です。 さたざたなタむプを詊しおみるず、次の衚が埗られたす。



タむプたたはその定矩 倧きさ 解説
察象 12 SyncBlk + VMT +空のフィヌルド

Int16 12 Boxed Int16SyncBlk + VMT +デヌタ

x86では4バむトで敎列

Int32 12 Boxed Int32SyncBlk + VMT +デヌタ

Int64 16 Boxed Int64SyncBlk + VMT +デヌタ

チャヌ 12 囲み文字SyncBlk + VMT +デヌタ

x86では4バむトで敎列

ダブル 16 Boxed DoubleSyncBlk + VMT +デヌタ

IEnumerable 0 むンタヌフェむスにはサむズがありたせんobj.GetTypeを取る必芁がありたす

リスト[T] 24 リスト[T]内のアむテムの数は関係ありたせん

同じだろう 考慮されおいない配列にデヌタを保存したす

GenericSample [int] 12 ご芧のずおり、ゞェネリックは玠晎らしいです。 サむズは倉曎されおいたせん

なぜなら デヌタは、boxed intず同じ堎所にありたす。

結果SyncBlk + VMT + data = 12バむトx86

GenericSample [Int64] 16 同様に

GenericSample [IEnumerable] 12 同様に

GenericSample [DateTime] 16 同様に

ひも 14 この倀は、すべおの行に察しお返されたす。

なぜなら 実際のサむズは動的に考慮する必芁がありたす。

ただし、空の文字列のサむズには適しおいたす。

サむズが調敎されおいないこずに泚意しおください

ビット深床基本的にこのフィヌルドが䜿甚されたす

しおはいけない

int [] {1} 24554 この堎所にある配列の堎合

完党に異なるデヌタずそのサむズは異なりたす

考慮される必芁があるため、修正

別に



ご芧のずおり、システムが型のむンスタンスのサむズでデヌタを保存するずき、実際には参照型のデヌタ参照バリアントの重芁なものを含むが保存されたす。 いく぀かの結論を導きたしょう。



  1. 重芁な型が倀ずしおどれだけ取るかを知りたい堎合は、 `sizeofTType`を䜿甚したす
  2. ボクシングにかかる​​コストを蚈算する堎合は、「sizeofTType」をプロセッサのワヌドサむズ4たたは8バむトに切り䞊げ、さらに2ワヌド远加できたす。 たたは、この倀を「VMT」タむプから取埗したす。
  3. 必芁に応じお、ヒヌプにメモリを割り圓おるのにかかるコストを理解するために、3぀のオプションがありたす。


System.string



実甚的な問題の行に぀いおは別に説明したす。この比范的小さなクラスに章党䜓を割り圓おるこずができたす。 たた、VMTの構築に関する章では、䜎レベルでのラむンの構築に぀いお説明したす。 UTF16暙準は、文字列を栌玍するために䜿甚されたす。 ぀たり、各文字には2バむトが必芁です。 さらに、各行の最埌にヌルタヌミネヌタが栌玍されたす぀たり、行が終了したこずを識別する倀。 文字列の長さは、Int32番号ずしおも保存されたす-必芁になるたびに長さをカりントしないようにするためです。 ゚ンコヌディングに぀いおは別に説明したすが、今のずころはこの情報で十分です。



  //  .NET Framework 4   ------------------------------------------------------------------------- | SyncBlkIndx | VMTPtr | Length | char | char | Term | ------------------------------------------------------------------------- | 4 / 8  | 4 / 8  | 4  | 2 . | 2 . | 2 . | ------------------------------------------------------------------------- | -1 | 0xXXXXXXXX | 2 | a | b | nil | ------------------------------------------------------------------------- Term - null terminator Sum size = (12 (24) + 2 + (Len*2)) ->      . (20   ) //  .NET Framework 3.5   ------------------------------------------------------------------------------ | SyncBlkIndx| VMTPtr | ArrayLength | Length | char | char | Term | ------------------------------------------------------------------------------ | 4 / 8  | 4 / 8  | 4  | 4  | 2 . | 2 . | 2 . | ------------------------------------------------------------------------------ | -1 | 0xXXXXXXXX | 3 | 2 | a | b | nil | ------------------------------------------------------------------------------ Term - null terminator Sum size = (16 (32) + 2 + (Len*2)) ->      . (24   )
      
      





文字列のサむズを数える方法を教えるためにメ゜ッドを曞き盎したす



 unsafe int SizeOf(object obj) { var majorNetVersion = Environment.Version.Major; var type = obj.GetType(); var href = Union.GetRef(obj).ToInt64(); var DWORD = sizeof(IntPtr); var baseSize = 3 * DWORD; if (type == typeof(string)) { if (majorNetVersion >= 4) { var length = (int)*(int*)(href + DWORD /* skip vmt */); return DWORD * ((baseSize + 2 + 2 * length + (DWORD-1)) / DWORD); } else { // on 1.0 -> 3.5 string have additional RealLength field var arrlength = *(int*)(href + DWORD /* skip vmt */); var length = *(int*)(href + DWORD /* skip vmt */ + 4 /* skip length */); return DWORD * ((baseSize + 2 + 2 * length + (DWORD -1)) / DWORD); } } else if (type.BaseType == typeof(Array) || type == typeof(Array)) { return ((ArrayInfo*)href)->SizeOf(); } return SizeOf(type); }
      
      





`SizeOftype`は叀い実装を呌び出したす-固定長の参照型の堎合。



実際にコヌドを確認したしょう



  Action<string> stringWriter = (arg) => { Console.WriteLine($"Length of `{arg}` string: {SizeOf(arg)}"); }; stringWriter("a"); stringWriter("ab"); stringWriter("abc"); stringWriter("abcd"); stringWriter("abcde"); stringWriter("abcdef"); } ----- Length of `a` string: 16 Length of `ab` string: 20 Length of `abc` string: 20 Length of `abcd` string: 24 Length of `abcde` string: 24 Length of `abcdef` string: 28
      
      





蚈算では、行サむズは線圢ではなく段階的に、぀たり2文字ごずに増加したす。 これは、各文字のサむズが2バむトであり、互いに続くためです。 ただし、最終サむズは、トレヌスなしでプロセッサの容量で割る必芁がありたす。 すなわち 䞀郚の行は、さらに2バむト「アップ」したす。 私たちの仕事の結果は玠晎らしいです。この行がどれくらいの費甚がかかるかを蚈算できたす。 最埌のステップは、メモリ内の配列のサむズを蚈算し、タスクをさらに実甚的にする方法を芋぀けるこずです。次の質問に答えるメ゜ッドを䜜成したしょう。SOHに適合するためには、どのサむズの配列を取る必芁があるか。 Lengthプロパティを䜿甚するず、より合理的で高速になるように思えるかもしれたせんが、実際には、動䜜が遅くなりたす远加コスト。



配列



配列の構造はやや耇雑です配列は構造のバリアントを持぀こずができたす



  1. 意味のある型を保存するこずも、参照を保存するこずもできたす
  2. 配列には1぀たたは耇数の次元を含めるこずができたす
  3. 各枬定は、「0」たたは他の数字で開始できたすこれは、私の意芋では非垞に議論の䜙地のある機䌚です。


したがっお、配列の実装には倚少の混乱があり、最終的な配列のサむズを正確に予枬するこずができたせん。芁玠の数にサむズを掛けるだけでは䞍十分です。 もちろん、ほずんどの堎合、これでほが十分です。 LOHに入るのが怖いずき、サむズは重芁になりたす。 ただし、ここにはオプションがありたす。85000で境界を越えたかどうかを理解するために、「膝の䞊」で蚈算されたサむズに䞊蚘の定数100などを単玔にスロヌできたす。 ただし、このセクションのフレヌムワヌク内では、タスクは倚少異なりたす。型の構造を理解するこずです。 そしおそれを芋おください



 //  -------------------------------------------------------------------------------- | SBI | VMTPtr |Total | Len_1 | Len_2 | .. | Len_N | Term | VMT_Child | --------------------------opt-------opt------------opt-------opt--------opt----- | 4 / 8 | 4 / 8 | 4 | 4 | 4 | | 4 | 4 | 4/8 | -------------------------------------------------------------------------------- |0xFF.FF|0xXX.XX | ? | ? | ? | | ? |0x00.00| 0xXX..XX | -------------------------------------------------------------------------------- - opt:  - SBI: Sync Block Index - VMT_Child:         - Total:   .         - Len_2..Len_N + Term:       1 (   VMT->Flags)
      
      





ご芧のずおり、タむプヘッダヌには配列の次元に関するデヌタが栌玍されたす。その数は1たたは非垞に倧きい可胜性がありたす。実際、サむズはnullタヌミネヌタヌによっおのみ制限されたす。぀たり、列挙が完了したす。 この䟋は[GettingInstanceSize]ファむル./ samples / GettingInstanceSize.linqで完党に利甚できたす。以䞋では、最も重芁な郚分のみを瀺したす。



 public int SizeOf() { var total = 0; int elementsize; fixed (void* entity = &MethodTable) { var arr = Union.GetObj<Array>((IntPtr)entity); var elementType = arr.GetType().GetElementType(); if (elementType.IsValueType) { var typecode = Type.GetTypeCode(elementType); switch (typecode) { case TypeCode.Byte: case TypeCode.SByte: case TypeCode.Boolean: elementsize = 1; break; case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Char: elementsize = 2; break; case TypeCode.Int32: case TypeCode.UInt32: case TypeCode.Single: elementsize = 4; break; case TypeCode.Int64: case TypeCode.UInt64: case TypeCode.Double: elementsize = 8; break; case TypeCode.Decimal: elementsize = 12; break; default: var info = (MethodTable*)elementType.TypeHandle.Value; elementsize = info->Size - 2 * sizeof(IntPtr); // sync blk + vmt ptr break; } } else { elementsize = IntPtr.Size; } // Header total += 3 * sizeof(IntPtr); // sync blk + vmt ptr + total length total += elementType.IsValueType ? 0 : sizeof(IntPtr); // MethodsTable for refTypes total += IsMultidimentional ? Dimensions * sizeof(int) : 0; } // Contents total += (int)TotalLength * elementsize; // align size to IntPtr if ((total % sizeof(IntPtr)) != 0) { total += sizeof(IntPtr) - total % (sizeof(IntPtr)); } return total; }
      
      





このコヌドは、配列タむプのすべおのバリ゚ヌションを考慮し、そのサむズを蚈算するために䜿甚できたす。



 Console.WriteLine($"size of int[]{{1,2}}: {SizeOf(new int[2])}"); Console.WriteLine($"size of int[2,1]{{1,2}}: {SizeOf(new int[1,2])}"); Console.WriteLine($"size of int[2,3,4,5]{{...}}: {SizeOf(new int[2, 3, 4, 5])}"); --- size of int[]{1,2}: 20 size of int[2,1]{1,2}: 32 size of int[2,3,4,5]{...}: 512
      
      





セクションの結論



この段階で、かなり重芁なこずをいく぀か孊びたした。 最初に、参照型を固定型の参照型、ゞェネリック型、および可倉サむズの参照型の3぀のグルヌプに分けたした。 たた、あらゆるタむプの最終むンスタンスの構造を理解するこずも孊びたした圓面、VMTの構造に぀いおは蚀及したせん。これたでのずころ、1぀のフィヌルドのみを完党に理解したした。これも玠晎らしい成果です。 固定サむズの参照型すべおが非垞に単玔たたは䞍定サむズの参照型配列たたは文字列。 サむズが䜜成時に決定されるため、䞍確かです。 実際、ゞェネリック型を䜿甚するず、すべおが単玔になりたす。特定のゞェネリック型ごずに、独自のVMTが䜜成され、そこに特定のサむズが付加されたす。



メ゜ッド衚



VMTクラス



メ゜ッドテヌブルの説明はほずんど孊術的なものです。結局のずころ、そのようなゞャングルに登るずいうこずは、自分で墓を掘るようなものです。 䞀方では、そのようなビンぱキサむティングで興味深いものを隠し、䜕が起こっおいるのかをさらに明らかにするデヌタを保存したす。 ただし、䞀方で、Microsoftはランタむムを倉曎せず、たずえば、突然メ゜ッドテヌブルが1぀のフィヌルドに移動しないずいう保蚌をMicrosoftが提䟛しないこずを理解しおいたす。



たあ、すべおが譊告した。 今、圌らが芋おいるガラスを通しお蚀うように、䞖界に飛び蟌みたしょう。 実際、これたでは、芋おいるガラス党䜓がオブゞェクトの構造の知識に還元されおきたした。そしお、理論的には、少なくずもおおよそ知っおいるはずです。 そしお本質的に、この知識は芋おいるガラスの背埌にあるのではなく、芋おいるガラスぞの入り口です。 CoreCLRで説明されおいる `MethodTable`構造に戻りたしょう。



  // Low WORD is component size for array and string types (HasComponentSize() returns true). // Used for flags otherwise. DWORD m_dwFlags; // Base size of instance of this class when allocated on the heap DWORD m_BaseSize; WORD m_wFlags2; // Class token if it fits into 16-bits. If this is (WORD)-1, the class token is stored in the TokenOverflow optional member. WORD m_wToken; // <NICE> In the normal cases we shouldn't need a full word for each of these </NICE> WORD m_wNumVirtuals; WORD m_wNumInterfaces;
      
      





぀たり、「m_wNumVirtuals」および「m_wNumInterfaces」フィヌルドに。 これらの2぀のフィヌルドは、「タむプに含たれる仮想メ゜ッドずむンタヌフェヌスの数」ずいう質問に察する答えを決定したす。 この構造には、通垞のメ゜ッド、フィヌルド、プロパティメ゜ッドを結合するに関する情報はなく、**はリフレクション**に関連付けられおいたせん。 その本質ず目的により、この構造は、CLRでのメ゜ッド呌び出しの操䜜のために䜜成されたした実際、すべおのOOPで、Java、C ++、Rubyなどです。フィヌルドの堎所だけが少し異なりたす。 コヌドを芋おみたしょう



  public class Sample { public int _x; public void ChangeTo(int newValue) { _x = newValue; } public virtual GetValue() { return _x; } } public class OverridedSample : Sample { public override GetValue() { return 666; } }
      
      





これらのクラスが無意味に芋えおも、VMTを蚘述するのに非垞に適しおいたす。 このために、基本型ず、質問で継承された `ChangeTo`メ゜ッドず` GetValue`メ゜ッドの違いを理解する必芁がありたす。



`ChangeTo`メ゜ッドは䞡方のタむプに存圚したす再定矩できたせん。 ぀たり、次のように曞き換えるこずができたす。



 public class Sample { public int _x; public static void ChangeTo(Sample self, int newValue) { self._x = newValue; } // ... } //        struct public struct Sample { public int _x; public static void ChangeTo(ref Sample self, int newValue) { self._x = newValue; } // ... }
      
      





同時に、アヌキテクチャ䞊の意味を陀けば、䜕も倉わりたせん。䞡方のオプションをコンパむルしおも同じように機胜するこずを信じおください。 むンスタンスメ゜ッドの堎合、 `this`は暗黙的に枡されるメ゜ッドの最初のパラメヌタヌにすぎたせん。



継承に関するすべおの説明が静的メ゜ッドの䟋に基づいお構築されおいる理由を事前に説明したす。実際、すべおのメ゜ッドは静的です。 むンスタンスではなく。 メモリ内のクラスの各むンスタンスにコンパむルされたメ゜ッドのむンスタンスはありたせん。 これは倧量のメモリを消費したす。同じメ゜ッドが、それが機胜する構造䜓たたはクラスのむンスタンスぞのリンクを枡す方が簡単です。



GetValueメ゜ッドの堎合、状況はたったく異なりたす。 継承された型の* static * `GetValue`をオヌバヌラむドしおメ゜ッドを取埗および再定矩するこずはできたせん。倉数で動䜜するコヌドの郚分である` OverridedSample`のみが新しいメ゜ッドを受け取りたす。オブゞェクトが実際にどの型であるかわからないため、ベヌス型の「GetValue」のみを呌び出すこずができたす。 倉数がどの型であり、その結果、どの特定のメ゜ッドが呌び出されるかを理解するために、次のこずができたす。



 void Main() { var sample = new Sample(); var overrided = new OverridedSample(); Console.WriteLine(sample.Virtuals[Sample.GetValuePosition].DynamicInvoke(sample)); Console.WriteLine(overrided.Virtuals[Sample.GetValuePosition].DynamicInvoke(sample)); } public class Sample { public const int GetValuePosition = 0; public Delegate[] Virtuals; public int _x; public Sample() { Virtuals = new Delegate[1] { new Func<Sample, int>(GetValue) }; } public static void ChangeTo(Sample self, int newValue) { self._x = newValue; } public static int GetValue(Sample self) { return self._x; } } public class OverridedSample : Sample { public OverridedSample() : base() { Virtuals[0] = new Func<Sample, int>(GetValue); } public static new int GetValue(Sample self) { return 666; } }
      
      





この䟋では、実際に仮想メ゜ッドのテヌブルを手動で䜜成し、このテヌブル内のメ゜ッドの䜍眮で呌び出しを行いたす。 䟋の本質を理解すれば、コンパむルされたコヌドレベルで継承がどのように構築されるかを実際に理解できたす。メ゜ッドは、仮想メ゜ッドテヌブルのむンデックスで呌び出されたす。 , . VMT , , : , . , .














All Articles