vptr の使用は標準の範囲外です。
確かに、memcpy
ここでの使用にはUBがあります
の使用memcpy
、または非 POD、つまり vptr を持つオブジェクトのその他のバイト操作には未定義の動作があることを指摘する回答は、厳密に技術的に正しいですが、質問には答えません。質問は、標準によって義務付けられていない vptr (vtable ポインター) の存在に基づいています。もちろん、答えには標準外の事実が含まれ、結果の請求書は標準によって保証されません!
標準テキストは、vptr に関しては関係ありません
問題は、vptr の操作が許可されていないということではありません。標準テキストに記述さえされていないものを操作することが標準によって許可されているという考えはばかげています。もちろん、vptr を変更する標準的な方法は存在しませんが、これは重要ではありません。
vptr は、ポリモーフィック オブジェクトの型をエンコードします。
ここでの問題は、標準が vptr について述べていることではありません。問題は vptr が何を表しているか、そしてそれについて標準が何を述べているかです。vptr はオブジェクトの動的な型を表します。演算の結果が動的型に依存する場合は常に、コンパイラは vptr を使用するコードを生成します。
[MI に関する注意: 「その」vptr (vptr が 1 つだけであるかのように) と言いますが、MI (多重継承) が関係する場合、オブジェクトは複数の vptr を持つことができ、それぞれが特定のポリモーフィックな基本クラスと見なされる完全なオブジェクトを表します。タイプ。(ポリモーフィック クラスとは、少なくとも 1 つの仮想関数を持つクラスです。)]
[仮想ベースに関する注意: vptr についてのみ言及しますが、仮想ベース サブオブジェクトの場所など、動的型の側面を表すために他のポインターを挿入するコンパイラもあれば、その目的で vptr を使用するコンパイラもあります。vptr について正しいことは、これらの他の内部ポインターについても正しいです。]
したがって、vptr の特定の値は動的な型に対応します。これは、最も派生したオブジェクトの型です。
存続期間中のオブジェクトの動的タイプの変更
構築中に動的型が変更されるため、コンストラクター内からの仮想関数呼び出しは「驚くべき」ものになる可能性があります。構築中に仮想関数を呼び出す規則は特別だと言う人もいますが、絶対にそうではありません。そのオーバーライドは、構築された最も派生したオブジェクトに対応するクラスであり、コンストラクターC::C(arg-list)
では、常にクラスの型ですC
。
破棄中、動的型は逆の順序で変更されます。デストラクタ内からの仮想関数の呼び出しは、同じ規則に従います。
何かが未定義のままになっている場合の意味
標準で認可されていない低レベルの操作を行うことができます。動作が C++ 標準で明示的に定義されていないということは、それが他の場所で説明されていないことを意味するものではありません。操作の結果が C++ 標準で UB (未定義の動作) を持つように明示的に記述されているからといって、実装でそれを定義できないわけではありません。
コンパイラの動作方法に関する知識を使用することもできます。厳密な個別コンパイルが使用される場合、つまり、コンパイラが個別にコンパイルされたコードから情報を取得できない場合、個別にコンパイルされたすべての関数は「ブラック ボックス」になります。この事実を利用できます。コンパイラは、個別にコンパイルされた関数が実行できることはすべて実行されると想定する必要があります。特定の関数内であっても、asm
ディレクティブを使用して同じ効果を得るasm
ことができます。制約のないディレクティブは、C++ で有効なすべてのことを行うことができます。その効果は、「その時点でコード分析から知っていることを忘れる」という指示です。
標準では、何が動的型を変更できるかが説明されており、構築/破壊以外は何も変更できないため、「外部」(ブラックボックス) 関数のみが構築/破壊の実行を許可され、動的型を変更できます。
[basic.life]/8を参照してください。
オブジェクトの有効期間が終了した後、オブジェクトが占有していたストレージが再利用または解放される前に、元のオブジェクトが占有していたストレージの場所に新しいオブジェクトが作成された場合、元のオブジェクトを指すポインタ、その参照または、元のオブジェクトの名前が自動的に新しいオブジェクトを参照し、新しいオブジェクトの有効期間が開始されると、新しいオブジェクトを操作するために使用できます。
(8.1) 新しいオブジェクトのストレージは、元のオブジェクトが占めていたストレージの場所を正確にオーバーレイします。
(8.2) 新しいオブジェクトが元のオブジェクトと同じ型である (最上位の cv 修飾子を無視する)、および
(8.3) 元のオブジェクトの型が const 修飾されておらず、クラス型の場合、その型が const 修飾または参照型である非静的データ メンバーを含まない。
(8.4) 元のオブジェクトは型 T の最派生オブジェクト ([intro.object]) であり、新しいオブジェクトは型 T の最派生オブジェクトです (つまり、これらは基本クラスのサブオブジェクトではありません)。
これは、コンストラクターを (placement new で) 呼び出して、オブジェクト (名前、ポインターなど) を指定するために使用したのと同じ式を引き続き使用できる唯一のケースは、動的型が変更されない場合であることを意味します。そのため、vptr は同じままです。
つまり、低レベルのトリックを使用して vptr を上書きしたい場合は、次のことができます。ただし、同じ値を書き込む場合のみ。
つまり、vptr をハッキングしようとしないでください。