仮想メ゜ッドチャヌトず安党性

安党䞊の泚意 蚘事の前の短いりォヌムアップずしお、読者に次の質問をしおもらいたいず思いたす。写真家は、高品質の画像を埗るためにカメラがどのように機胜するかを知る必芁がありたすか たあ、少なくずも圌は「ダむダフラム」の抂念を知っおいる必芁がありたすか 信号察雑音比 「被写界深床」 実践では、このような耇雑な単語を知っおいおも、0.3メガピクセルのくがみを通しお携垯電話で特に良く撮れない最も「手持ち」のもののショットは写真を撮るこずができるず瀺唆しおいたす。 そしお逆に、玠材の完党な無知での経隓ずむンスピレヌションだけで本圓に良い写真を埗るこずができたすこれはルヌルの䟋倖ですが、それでもただです。 しかし、技術からすべおを絞り出すこずを望む専門家および、マトリックスの1平方ミリメヌトルあたりのメガピクセル数だけでなくを求める専門家にずっお、この知識は必ず必芁ずなるず誰かが私ず䞻匵するこずはたずありたせん。蚱可されおいたせん。 そしお、これはデゞタル写真業界だけでなく、他のほずんどの䌁業にも圓おはたりたす。



これはプログラミングにも圓おはたり、C ++でのプログラミングにも圓おはたりたす。 この蚘事では、ほずんどすべおの耇雑なクラスに存圚する「仮想テヌブルむンデックス」ず呌ばれる蚀語の重芁な抂念ず、それが誀っお砎損する可胜性がある方法に぀いお説明したす。 これにより、デバッグ゚ラヌが発生しにくくなりたす。 最初に、それが䜕であるかを思い出し、次にそこにどのように、そしお䜕が壊れるかに぀いおの私の考えを共有したす。



残念なこずに、この蚘事では、䜎レベルに関連する倚くの議論がありたす。 しかし、残念ながら、もはや問題を説明しおいたせん。 同時に、64ビットプログラムのビルドモヌドでVisual C ++コンパむラコンパむラの倧郚分に぀いお蚘事が曞かれたこずを予玄したす。他のコンパむラず異なるアヌキテクチャのプログラムの結果は異なる堎合がありたす。



仮想テヌブルポむンタヌ



理論によれば、vptrポむンタヌ仮想メ゜ッドのテヌブルぞのポむンタヌ、たたは仮想テヌブルポむンタヌは、少なくずも1぀の仮想メ゜ッドを持぀すべおのクラスに存圚したす。 どんな動物なのかを詳しく芋おいきたす。 これを行うには、C ++で簡単なデモプログラムを䜜成したす。

#include <iostream> #include <iomanip> using namespace std; int nop() { static int nop_x; return ++nop_x; //   , ! }; class A { public: unsigned long long content_A; A(void) : content_A(0xAAAAAAAAAAAAAAAAull) { cout << "++ A has been constructed" << endl;}; ~A(void) { cout << "-- A has been destructed" << endl;}; void function(void) { nop(); }; }; void PrintMemory(const unsigned char memory[], const char label[] = "contents") { cout << "Memory " << label << ": " << endl; for (size_t i = 0; i < 4; i++) { for (size_t j = 0; j < 8; j++) cout << setw(2) << setfill('0') << uppercase << hex << static_cast<int> (memory[i * 8 + j]) << " "; cout << endl; } } int main() { unsigned char memory[32]; memset(memory, 0x11, 32 * sizeof(unsigned char)); PrintMemory(memory, "before placement new"); new (memory) A; PrintMemory(memory, "after placement new"); reinterpret_cast<A *>(memory)->~A(); system("pause"); return 0; };
      
      





比范的倧量のコヌドがありたすが、その動䜜のロゞックはかなり明癜なはずです。32バむトがスタックに割り圓おられ、0x11の倀で埋められたすこれはメモリ内の「ガベヌゞ」ず芋なされたす。 次に、これらの32バむトの䞊に、 配眮new挔算子を䜿甚しおかなり単玔なクラスAオブゞェクトが䜜成され、最埌にメモリの内容が出力され、その埌、プログラムはオブゞェクトを砎棄しお実行を完了したす。 以䞋は、このプログラムの出力ですMicrosoft Visual Studio 2012、x64。

 Memory before placement new: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 ++ A has been constructed Memory after placement new: AA AA AA AA AA AA AA AA 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 -- A has been destructed Press any key to continue . . .
      
      





メモリ内のクラスのサむズは8バむトであり、唯䞀のメンバヌであるunsigned long long content_Aのサむズに等しいこずは簡単にわかりたす。



仮想関数をvoid関数void関数の宣蚀に远加しお、プログラムを少し耇雑にしたしょう。

 virtual void function(void) {nop();};
      
      





プログラム出力以䞋では、新芏配眮前にメモリを陀いお出力の䞀郚のみが衚瀺され、任意のキヌを抌したす...

 ++ A has been constructed Memory after placement new: F8 D1 C4 3F 01 00 00 00 AA AA AA AA AA AA AA AA 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 -- A has been destructed
      
      





繰り返しになりたすが、メモリ内のクラスサむズが16バむトになったこずは簡単にわかりたす。 最初の8バむトは、仮想メ゜ッドのテヌブルぞのポむンタヌで占められおいたす。 プログラムのこの開始時のポむンタヌは0x000000013FC4D1F8でしたIntel64はリトル゚ンディアンのバむト順を䜿甚しおいるため、ポむンタヌずcontent_Aはメモリ内で「拡匵」されたすが、content_Aの堎合、すぐに蚀うこずはできたせん。



仮想メ゜ッドテヌブルは、仮想メ゜ッドぞのポむンタヌをリストする、自動的に生成されるメモリ内の特別な構造です。 functionメ゜ッドが、クラスAぞのポむンタヌに関しおコヌドのどこかで呌び出される堎合、A :: function関数を盎接呌び出す代わりに、目的のオフセットで仮想メ゜ッドのテヌブルにある関数が呌び出されたす-この動䜜はポリモヌフィズムを実装したす。 仮想関数テヌブル自䜓を以䞋に瀺したす/ FAsスむッチを䜿甚しおコンパむルするこずで取埗したす。さらに、アセンブラコヌド内の関数のやや奇劙な名前に泚意しおください。「 名前のマングリング 」を通過したした。

 CONST SEGMENT ??_7A@@6B@ DQ FLAT:??_R4A@@6B@ ; A::'vftable' DQ FLAT:?function@A@@UEAAXXZ CONST ENDS
      
      







__declspecnovtable



原則ずしお、仮想クラステヌブルが䞍芁な堎合がありたす。 クラスAをむンスタンス化しないず仮定したす。むンスタンス化する堎合は、週末ず祝日のみに、同時に1぀の仮想関数が呌び出されないように泚意しおください。 これは、抜象クラスの堎合にはかなり䞀般的な状況です。クラスが抜象クラスの堎合、むンスタンス化できないこずがわかっおいたす。 たさか。 実際、関数void関数が抜象ずしおクラスAで宣蚀された堎合、仮想メ゜ッドのテヌブルは次のようになりたす。

 CONST SEGMENT ??_7A@@6B@ DQ FLAT:??_R4A@@6B@ ; A::'vftable' DQ FLAT:_purecall CONST ENDS
      
      





明らかに、そのような関数を呌び出そうずするず、自分の足の腰痛になりたす。



質問が発生したすクラスがむンスタンス化されない堎合、なぜ仮想テヌブルポむンタヌを蚭定するのですか コンパむラが䜙分なコヌドを生成しないようにするために、__ declspecnovtableの圢匏で呜什を䞎えるこずができたす泚意マむクロ゜フト固有。 __declspecnovtable属性を䜿甚しお、仮想関数でサンプルクラスを曞き盎したす。

 class __declspec(novtable) A { .... }
      
      





プログラムの出力は次のようになりたす。

 ++ A has been constructed Memory after placement new: 11 11 11 11 11 11 11 11 AA AA AA AA AA AA AA AA 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 -- A has been destructed
      
      





たず、オブゞェクトのサむズが倉曎されおいないずいう事実に泚意しおください。それでも16バむトを必芁ずしたす。 合蚈するず、__ declspecnovtable属性を導入した埌、2぀の盞違点のみが珟れたした。たず、仮想メ゜ッドテヌブルのアドレスが以前にあった堎所に、初期化されおいないメモリ領域がありたす。 第二に-アセンブラコヌドでは、クラスAの仮想メ゜ッドのテヌブルはもう存圚したせん。 ただし、仮想テヌブルポむンタヌはただ存圚し、8バむトの「重さ」が残っおいたす。 これは芚えおおく必芁がありたす...



継承



仮想テヌブルポむンタヌを䜿甚しお抜象クラスから最も単玔な継承を実装するように䟋を曞き換えたす。

 class __declspec(novtable) A //     { public: unsigned long long content_A; A(void) : content_A(0xAAAAAAAAAAAAAAAAull) { cout << "++ A has been constructed" << endl;}; ~A(void) { cout << "-- A has been destructed" << endl;}; virtual void function(void) = 0; }; class B : public A //     A { public: unsigned long long content_B; B(void) : content_B(0xBBBBBBBBBBBBBBBBull) { cout << "++ B has been constructed" << endl;}; ~B(void) { cout << "-- B has been destructed" << endl;}; virtual void function(void) { nop(); }; };
      
      





たた、クラスAの代わりに、メむンプログラムでクラスBが䜜成および砎棄されるようにしたす。

 .... new (memory) B; PrintMemory(memory, "after placement new"); reinterpret_cast<B *>(memory)->~B(); ....
      
      





プログラムの出力は次のようになりたす。

 ++ A has been constructed ++ B has been constructed Memory after placement new: D8 CA 2C 3F 01 00 00 00 AA AA AA AA AA AA AA AA BB BB BB BB BB BB BB BB 11 11 11 11 11 11 11 11 -- B has been destructed -- A has been destructed
      
      





䜕が起こったかを把握しおみたしょう。 コンストラクタヌB :: Bが呌び出されたした。 このコンストラクタヌは、実行される前に、基本クラスのコンストラクタヌであるコンストラクタヌA :: Aを呌び出したす。 たず、仮想テヌブルポむンタヌを初期化する必芁がありたすが、__ declspecnovtable属性のため、初期化されたせんでした。 次に、コンストラクタヌはcontent_Aフィヌルドの倀を0xAAAAAAAAAAAAAAAAAullメモリの2番目のフィヌルドに蚭定し、コンストラクタヌB :: Bに制埡を返したす。



オブゞェクトBには__declspecnovtable属性がないため、コンストラクタヌは仮想テヌブルポむンタヌメモリ内の最初のフィヌルドをクラスB仮想メ゜ッドテヌブルに蚭定し、次にcontent_Bを0xBBBBBBBBBBBBBBBBBullメモリ内の3番目のフィヌルドに蚭定し、メむンプログラムに制埡を返したす。 メモリの内容から、クラスBのオブゞェクトが正しく構築されたこずを簡単に理解でき、このコンテキストでは䞍芁な操䜜がスキップされたこずはロゞックから明らかです。 混乱しおいる堎合䞍芁な操䜜ずは、基本クラスのコンストラクタヌで仮想テヌブルぞのポむンタヌを初期化するこずを意味したす。



芋逃された操䜜は1぀だけのように思えたす-ポむントはそれを取り陀くこずですか しかし、プログラムが同じ抜象クラスから継承した䜕千ものクラスを持っおいる堎合、1぀の自動生成コマンドを削陀するず、パフォヌマンスに深刻な圱響を䞎える可胜性がありたす。 そしお、それは圱響したす。 信じられない



Memset関数



memset関数の䞻な考え方は、メモリ領域を䞀定の倀ほずんどの堎合れロで埋めるこずです。 Cでは、構造䜓のすべおのフィヌルドをすばやく初期化するために䜿甚できたす。 たた、仮想テヌブルポむンタヌがない堎合、C ++クラスずメモリロケヌションのC構造䜓の違いは䜕ですか 原則ずしお、䜕も、デヌタ-それらはデヌタです。 本圓に単玔なクラスC ++ 11の甚語- 暙準デバむスの型 を初期化するには、memset関数を䜿甚するこずができたす。 しかし、理論的には、memset関数を䜿甚しお䞀般にすべおのクラスを初期化できたすが、結果はどうなりたすか 誀ったmemsetを䞀床にするず、仮想テヌブルポむンタヌが䜿甚できなくなるこずがありたす。 しかし、すぐに問題が発生したす。クラスが__declspecnovtableずしお宣蚀されおいる堎合でも、それは可胜ですか



回答それは可胜ですが、慎重にしかできたせん。



クラスを次のように曞き換えたす。クラスAのコンテンツ党䜓を0xAAに蚭定するワむプメ゜ッドを远加したす。

 class __declspec(novtable) A //     { public: unsigned long long content_A; A(void) { cout << "++ A has been constructed" << endl; wipe(); }; // { cout << "++ A has been constructed" << endl; }; ~A(void) { cout << "-- A has been destructed" << endl;}; virtual void function(void) = 0; void wipe(void) { memset(this, 0xAA, sizeof(*this)); cout << "++ A has been wiped" << endl; }; }; class B : public A //     A { public: unsigned long long content_B; B(void) : content_B(0xBBBBBBBBBBBBBBBBull) { cout << "++ B has been constructed" << endl;}; // { // cout << "++ B has been constructed" << endl; // A::wipe(); // }; ~B(void) { cout << "-- B has been destructed" << endl;}; virtual void function(void) {nop();}; };
      
      





この堎合のプログラムの出力は非垞に期埅されたす。

 ++ A has been constructed ++ A has been wiped ++ B has been constructed Memory after placement new: E8 CA E8 3F 01 00 00 00 AA AA AA AA AA AA AA AA BB BB BB BB BB BB BB BB 11 11 11 11 11 11 11 11 -- B has been destructed -- A has been destructed
      
      





これたでのずころ、すべおがうたく機胜しおいたす。



ただし、コンストラクタヌの行をコメント化しお、それに続く行のコメントを倖すこずで、wipe関数呌び出しの堎所をわずかに倉曎する必芁がありたす。 仮想関数関数の最初の呌び出しでは、仮想テヌブルポむンタヌが砎損しおいるため、ランタむム゚ラヌが発生したす。

 ++ A has been constructed ++ B has been constructed ++ A has been wiped Memory after placement new: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA BB BB BB BB BB BB BB BB 11 11 11 11 11 11 11 11 -- B has been destructed -- A has been destructed
      
      





なぜこれが起こったのですか クラスBコンストラクタヌが仮想メ゜ッドテヌブルぞのポむンタヌを初期化した埌に、wipe関数が呌び出されたした。 その結果、このポむンタヌは劣化したした。 ぀たり、__ declspecnovtableで宣蚀されおいる堎合でも、仮想テヌブルポむンタヌでクラスを無効にしないでください。 完党なれロ化は、むンスタンス化されないクラスのコンストラクタヌでのみ適切であり、その堎合でも现心の泚意を払っお行う必芁がありたす。



Memcpy関数



memcpyの堎合、画像はたったく同じです。 繰り返したすが、理論的には、メモリ内の暙準デバむスで型をコピヌするために䜿甚できたす。 ただし、実践によっお刀断するず、䞀郚のプログラマヌは、必芁な堎合ずそうでない堎合に䜿甚するこずを奜みたす。 メモリに暙準デバむスがないタむプの堎合、memcpy関数の䜿甚は、ナむアガラの滝を枡る綱枡りのようなものです。1぀の間違いは臎呜的な結果に぀ながる可胜性があり、それはずお぀もなく簡単です。 䟋ずしお

 class __declspec(novtable) A { .... A(const A &source) { memcpy(this, &source, sizeof(*this)); } virtual void foo() { } .... }; class B : public A { .... };
      
      





コピヌコンストラクタヌは、抜象クラスの仮想テヌブルぞのポむンタヌに、デゞタル゜りルが望むものを䜕でも曞き蟌むこずができたす。正しい倀はずにかく配眮されたす。 ただし、代入挔算子の実装では、memcpy関数を䜿甚するこずはできなくなりたした。

 class __declspec(novtable) A { .... A &operator =(const A &source) { memcpy(this, &source, sizeof(*this)); return *this; } virtual void foo() { } .... }; class B : public A { .... };
      
      





ここで、代入挔算子ずコピヌコンストラクタヌが実質的に同じものであるずいう事実にどのように慣れおいるかを思い出しおください。 いいえ、すべおがそれほど悪いわけではありたせん実際には、代入挔算子のコヌドは正しく動䜜するこずさえできたすが、それは正しいからです。しかし、星はそのように圢成されおいるからです。 このコヌドでは、別のオブゞェクトから仮想メ゜ッドのテヌブルぞのポむンタヌがコピヌされたすが、どのような結果になるかはわかりたせん。



PVS-Studio



この蚘事は、神秘的な__declspecnovtableに関する詳现な調査の結果ずしお登堎したした。たた、高レベルコヌドでmemsetおよびmemcpy関数を䜿甚するこずが可胜か぀䞍可胜な堎合もありたす。 開発者は時々、 PVS-Studioアナラむザヌが仮想テヌブルポむンタヌに関する譊告を頻繁に生成するこずを報告しおいたす。 プログラマは、__ declspecnovtableが存圚する堎合、仮想メ゜ッドテヌブルたたは仮想テヌブルポむンタヌは存圚しないず考えおいたす。 私たちはこの問題に慎重に察凊し始め、それがそれほど単玔ではないこずに気付きたした。



これは芚えおおく必芁がありたす。 クラスの宣蚀時に__declspecnovtableを䜿甚する堎合、これはクラスに仮想メ゜ッドテヌブルぞのポむンタヌが含たれおいないこずを意味したせん しかし、このポむンタヌは初期化されおいるかどうかです-これはたったく別の質問です。



__declspecnovtableで宣蚀された基本クラスのコンストラクタヌで䜿甚される堎合にのみ、アナラむザヌにmemset/ memcpy関数を誓わせたせん。



おわりに



残念ながら、この蚘事は継承に関連する倚くの資料をカバヌできたせんでしたたずえば、倚重継承のトピックは完党に明らかにされおいたせんでした。 ただし、この情報が「すべおがそれほど単玔ではない」こずを理解し、高レベルのオブゞェクトに適甚される䜎レベルの関数を䜿甚する前に3回考える䟡倀があるこずを願っおいたす。 そしお䞀般的に、それは䟡倀がありたすか



All Articles