の適切な用途は何ですか:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- Cスタイルのキャスト
(type)value
- 関数スタイルのキャスト
type(value)
どのように特定のケースでどちらを使用するかをどのように決定しますか
の適切な用途は何ですか:
static_cast
dynamic_cast
const_cast
reinterpret_cast
(type)value
type(value)
どのように特定のケースでどちらを使用するかをどのように決定しますか
回答:
static_cast
使用しようとする最初のキャストです。これは、型間の暗黙的な変換(int
to float
やへのポインターなど)などを行い、void*
明示的な変換関数(または暗黙的な変換関数)を呼び出すこともできます。多くの場合、明示的に述べるstatic_cast
必要はありませんが、T(something)
構文は同等で(T)something
あり、避けるべきであることに注意することが重要です(詳細は後で説明します)。T(something, something_else)
ただし、A は安全で、コンストラクターの呼び出しが保証されています。
static_cast
継承階層をキャストすることもできます。上方向に(基本クラスに向かって)キャストする場合は不要ですが、下方向にキャストする場合は、virtual
継承を介してキャストしない限り使用できます。ただし、チェックは行われずstatic_cast
、階層を実際にオブジェクトのタイプではないタイプに下げることは未定義の動作です。
const_cast
const
変数を削除または追加するために使用できます。他のC ++キャストではそれを削除することはできません(であってもreinterpret_cast
)。以前のconst
値の変更は、元の変数がの場合にのみ未定義になることに注意することが重要ですconst
。を使用してconst
宣言されていないものへの参照を削除するconst
場合は、安全です。これはconst
、たとえばに基づいてメンバー関数をオーバーロードする場合に役立ちます。const
メンバー関数のオーバーロードを呼び出すなど、オブジェクトに追加するためにも使用できます。
const_cast
でも同様に機能しますがvolatile
、それほど一般的ではありません。
dynamic_cast
ポリモーフィズムの処理にのみ使用されます。ポインタまたはポリモーフィック型への参照を他のクラス型にキャストできます(ポリモーフィック型には、宣言または継承された少なくとも1つの仮想関数があります)。下向きにキャストするだけでなく、横向きにキャストしたり、別のチェーンにキャストしたりすることもできます。dynamic_cast
目的のオブジェクトを探し出し、可能であればそれを返します。できないnullptr
場合は、ポインターの場合は戻りstd::bad_cast
、参照の場合はスローします。
dynamic_cast
ただし、いくつかの制限があります。継承階層に同じタイプのオブジェクトが複数あり(いわゆる「ドレッドダイアモンド」)、virtual
継承を使用していない場合は機能しません。また、パブリック継承のみを通過できます。常に通過protected
またはprivate
継承に失敗します。ただし、このような形式の継承はまれであるため、これが問題になることはほとんどありません。
reinterpret_cast
最も危険なキャストであり、非常に慎重に使用する必要があります。ある型から別の型に直接値を変換します。たとえば、あるポインタから別のポインタに値をキャストしたり、ポインタをに格納しint
たり、あらゆる種類の厄介なものを格納したりします。主に、得られる唯一の保証reinterpret_cast
は、通常、結果を元の型にキャストすると、まったく同じ値が得られることです(ただし、中間型が元の型よりも小さい場合はそうではありません)。reinterpret_cast
できない変換もたくさんあります。これは主に、生データストリームを実際のデータに変換したり、整列されたデータへのポインターの下位ビットにデータを格納したりするなど、特に奇妙な変換やビット操作に使用されます。
Cスタイルのキャストと関数スタイルのキャストは、それぞれ(type)object
またはを使用したキャストtype(object)
であり、機能的に同等です。これらは、成功する次の最初のものとして定義されます。
const_cast
static_cast
(ただし、アクセス制限は無視されます)static_cast
(上記を参照)、次に const_cast
reinterpret_cast
reinterpret_cast
、その後 const_cast
したがって、場合によっては他のキャストの代わりとして使用できますが、にデボルブできるため、非常に危険になる可能性があります。reinterpret_cast
明示的なキャストが必要な場合は、static_cast
成功またはreinterpret_cast
失敗することが確実でない限り、後者が推奨されます。。それでも、より長く、より明示的なオプションを検討してください。
Cスタイルのキャストはstatic_cast
、を実行するときにアクセス制御も無視します。つまり、他のキャストではできない操作を実行する機能があります。ただし、これは大部分がクラッジであり、Cスタイルのキャストを回避するもう1つの理由にすぎません。
const
できません(さえもreinterpret_cast
)」...本当に?どうreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
ですか?
reinterpret_cast
は、APIの不透明なデータ型のセットを処理する際によく使用される武器であることに言及する価値があるかもしれません
dynamic_cast
継承階層内のポインター/参照の変換に使用します。
static_cast
通常の型変換に使用します。
reinterpret_cast
ビットパターンの低レベルの再解釈に使用します。細心の注意を払って使用してください。
const_cast
離れてキャストするために使用しますconst/volatile
。const-incorrect APIを使用してスタックしない限り、これを回避してください。
(多くの理論的および概念的な説明が上に与えられました)
以下は、いくつかのある具体例私が使用しstatic_castを、dynamic_castを、const_castを、reinterpret_castはは。
(また、説明を理解するためにこれを参照します:http ://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
OnEventData(void* pData)
{
......
// pData is a void* pData,
// EventData is a structure e.g.
// typedef struct _EventData {
// std::string id;
// std:: string remote_id;
// } EventData;
// On Some Situation a void pointer *pData
// has been static_casted as
// EventData* pointer
EventData *evtdata = static_cast<EventData*>(pData);
.....
}
dynamic_cast:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast:
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast:
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
static_cast<char*>(&val)
か?
static_cast
変換が定義されているタイプ間、継承による可視関係、またはto / fromの間でのみ機能しvoid *
ます。他のすべてのために、他のキャストがあります。reinterpret cast
任意のchar *
型へのアクセスは、任意のオブジェクトの表現の読み取りを許可されます-そして、そのキーワードが役立つ唯一のケースの1つであり、実装/未定義の動作の蔓延するジェネレータではありません。ただし、これは「通常の」変換とは見なされないため、(通常)非常に保守的な変換では許可されていませんstatic_cast
。
内部について少し知っていれば役立つかもしれません...
static_cast
static_cast
それらに使用します。A
にB
、static_cast
呼び出しB
のコンストラクタは、合格A
のparamとして。または、A
変換演算子(つまりA::operator B()
)を含めることもできます。そのB
ようなコンストラクタがない場合、またはA
変換演算子がない場合は、コンパイル時エラーが発生します。A*
はB*
常に成功します。そうでない場合、コンパイルエラーが発生します。A&
のB&
。dynamic_cast
(Base*)
する(Derived*)
ことがあります。A*
にB*
、キャストが無効である場合、その後はdynamic_castはnullptrを返します。A&
にB&
キャストが無効である場合、その後はdynamic_castはbad_cast例外がスローされます。const_cast
set<T>
、キーを変更しないことを確認するために、constとして要素のみを返すようなコンテナを反復処理することです。ただし、目的がオブジェクトの非キーメンバーを変更することである場合は、問題ありません。const_castを使用してconstnessを削除できます。T& SomeClass::foo()
と同様に実装する場合ですconst T& SomeClass::foo() const
。コードの重複を回避するために、const_castを適用して、ある関数の戻り値を別の関数から返すことができます。reinterpret_cast
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
.運がよければ、実行時にsegfaultが発生する可能性があるUBを取得します。2.ダイナミックキャストはクロスキャストにも使用できます。3. constキャストは、場合によってはUBになることがあります。を使用mutable
することは、論理的な定数を実装するためのより良い選択かもしれません。
mutable
、クロスキャスティングなどに伴う他のすべての複雑化で彼らを圧倒したくありませんでした
これは、あなたの質問に答えますか?
私はを使用reinterpret_cast
したことがなく、それを必要とするケースに出くわすことは、悪いデザインの匂いではないかと思います。私が取り組んでいるコードベースでdynamic_cast
は、よく使われています。との違い static_cast
は、dynamic_cast
ランタイムチェックが(安全)かそうでない(オーバーヘッドが多い)かを確認することです(msdnを参照)。
reinterpret_cast
は配列からデータの一部を抽出するために使用しています。たとえばchar*
、パックされたバイナリデータでいっぱいの大きなバッファが含まれている場合、移動してさまざまなタイプの個々のプリミティブを取得する必要があります。次のようなもの:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
したことがありません。その使用方法はそれほど多くありません。
reinterpret_cast
、1つの理由で使用されるのを見たことがあります。データベースの「blob」データ型に保存された生のオブジェクトデータを確認しました。その後、データベースからデータを取得すると、reinterpret_cast
この生のデータをオブジェクトに変換するために使用されます。
これまでの他の回答に加えて、これstatic_cast
は十分ではないためにreinterpret_cast
必要な明らかでない例です。出力パラメーターで異なるクラス(共通の基本クラスを共有しない)のオブジェクトへのポインターを返す関数があるとします。そのような関数の実際の例は次のとおりですCoCreateInstance()
(実際には最後のパラメーターを参照してくださいvoid**
)。この関数から特定のクラスのオブジェクトを要求するとします。これにより、ポインターのタイプ(COMオブジェクトに対してよく行う)が事前にわかります。この場合、あなたはあなたの中に、ポインタへのポインタをキャストすることはできませんvoid**
しstatic_cast
、あなたが必要ですreinterpret_cast<void**>(&yourPointer)
。
コードで:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
//static_cast<void**>(&pNetFwPolicy2) would give a compile error
reinterpret_cast<void**>(&pNetFwPolicy2) );
ただし、static_cast
(ポインターへのポインターではなく)単純なポインターで機能するため、上記のコードはreinterpret_cast
、次のように(追加の変数を犠牲にして)回避するように書き換えることができます。
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
&static_cast<void*>(pNetFwPolicy2)
代わりに次のように機能しませんstatic_cast<void**>(&pNetFwPolicy2)
か?
他の回答ではC ++キャスト間のすべての違いがうまく説明されていますが、Cスタイルのキャスト(Type) var
とを使用してはならない理由を簡単に説明しますType(var)
。
C ++初心者の場合、Cスタイルのキャストは、C ++キャスト(static_cast <>()、dynamic_cast <>()、const_cast <>()、reinterpret_cast <>())のスーパーセット操作のように見え、誰かがC ++キャストよりも好む可能性があります。 。実際、Cスタイルのキャストはスーパーセットであり、書くのに短いです。
Cスタイルのキャストの主な問題は、キャストの本当の意図を開発者が隠すことです。Cスタイルのキャストは、static_cast <>()およびdynamic_cast <>()によって行われる通常安全なキャストから、const修飾子を削除してconst変数を削除できるconst_cast <>()のような潜在的に危険なキャストまで、ほぼすべてのタイプのキャストを実行できます。変更してreinterpret_cast <>()を使用して、整数値をポインターに再解釈することもできます。
こちらがサンプルです。
int a=rand(); // Random number.
int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.
int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.
int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.
*pa4=5; // Program crashes.
C ++キャストが言語に追加された主な理由は、開発者が意図を明確にできるようにすることでした-なぜ彼がそのキャストを行うのか。C ++で完全に有効なCスタイルのキャストを使用することで、特にコードを作成しなかった他の開発者にとって、コードが読みにくくなり、エラーが発生しやすくなります。したがって、コードをより読みやすく明示的にするには、Cスタイルのキャストよりも常にC ++キャストを優先する必要があります。
これは、Bjarne Stroustrup(C ++の作者)の本、C ++プログラミング言語第4版-ページ302からの短い引用です。
このCスタイルのキャストは、名前付きの変換演算子よりもはるかに危険です。これは、大規模なプログラムでは表記法を見つけるのが難しく、プログラマーが意図する変換の種類が明確でないためです。
理解するために、以下のコードスニペットを考えてみましょう。
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
行(4)のみがエラーなしでコンパイルされます。reinterpret_castのみを使用して、オブジェクトへのポインターを任意の関連しないオブジェクト型へのポインターに変換できます。
これは、記載すべきものである:dynamic_castをを意味し、しかし、ほとんどのコンパイラで、それはまた、キャストされたポインタの構造体には仮想関数が存在しないため、コンパイルに失敗し、実行時に失敗していましたdynamic_castのが唯一の多型クラスのポインタで動作します。
C ++キャストを使用する場合:
static_cast
ダウンキャスト/アップキャストのvs dynamic_cast
vs reinterpret_cast
内部ビュー
この回答では、これら3つのメカニズムを具体的なアップキャスト/ダウンキャストの例で比較し、基になるポインター/メモリ/アセンブリがどうなるかを分析して、比較方法を具体的に理解したいと思います。
私はこれがそれらのキャストがどのように異なるかについて良い直感を与えると信じています:
static_cast
:実行時に1つのアドレスオフセットを実行し(実行時への影響が少ない)、ダウンキャストが正しいことの安全性チェックは行われません。
dyanamic_cast
:はと同様static_cast
に実行時に同じアドレスオフセットを実行しますが、RTTIを使用してダウンキャストが正しいかどうかのコストのかかる安全性チェックも行います。
この安全性チェックnullptr
では、無効なダウンキャストを示す戻り値をチェックすることにより、実行時に基本クラスポインターが特定の型であるかどうかを照会できます。
したがって、コードでそれを確認できずnullptr
、有効な非中止アクションを実行できない場合はstatic_cast
、動的キャストの代わりに使用する必要があります。
中止がコードで実行できる唯一のアクションである場合はdynamic_cast
、デバッグビルド(-NDEBUG
)を有効にし、static_cast
それ以外の場合、たとえばここで行ったように使用して、高速実行を遅くしないようにすることができます。
reinterpret_cast
:実行時には何もせず、アドレスオフセットもしません。ポインターは、正しい型を正確に指す必要があります。基本クラスでさえ機能しません。生のバイトストリームが関係しない限り、通常はこれを望まないでしょう。
次のコード例を検討してください。
main.cpp
#include <iostream>
struct B1 {
B1(int int_in_b1) : int_in_b1(int_in_b1) {}
virtual ~B1() {}
void f0() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
int int_in_b2;
};
struct D : public B1, public B2 {
D(int int_in_b1, int int_in_b2, int int_in_d)
: B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
void d() {}
int f2() { return 3; }
int int_in_d;
};
int main() {
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1, 2, 3};
// The memory layout must support the virtual method call use case.
b2s[0] = &b2;
// An upcast is an implicit static_cast<>().
b2s[1] = &d;
std::cout << "&d " << &d << std::endl;
std::cout << "b2s[0] " << b2s[0] << std::endl;
std::cout << "b2s[1] " << b2s[1] << std::endl;
std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;
// Now for some downcasts.
// Cannot be done implicitly
// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
// dp = (b2s[0]);
// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp = static_cast<D*>(b2s[0]);
std::cout << "static_cast<D*>(b2s[0]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = static_cast<D*>(b2s[1]);
std::cout << "static_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "static_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Segfault because dp is nullptr.
dp = dynamic_cast<D*>(b2s[0]);
std::cout << "dynamic_cast<D*>(b2s[0]) " << dp << std::endl;
//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;
// OK
dp = dynamic_cast<D*>(b2s[1]);
std::cout << "dynamic_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
// Undefined behaviour to an unrelated memory address because this
// did not calculate the offset to get from B2* to D*.
dp = reinterpret_cast<D*>(b2s[1]);
std::cout << "reinterpret_cast<D*>(b2s[1]) " << dp << std::endl;
std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}
コンパイル、実行、逆アセンブル:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
どこがsetarch
されて無効にASLRを使用し、それが簡単にランを比較することにします。
可能な出力:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
今、https://en.wikipedia.org/wiki/Virtual_method_tableで言及されているように、仮想メソッド呼び出しを効率的にサポートするために、のメモリデータ構造はD
次のようにする必要があります。
B1:
+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:
+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:
+0: pointer to virtual method table of D (for B1)
+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)
+12: value of int_in_b2
+16: value of int_in_d
重要な事実は、のメモリデータ構造が、内部および内部とD
互換性のあるメモリ構造を内部に含んでいることです。B1
B2
したがって、重要な結論に到達します。
アップキャストまたはダウンキャストは、コンパイル時に既知の値だけポインタ値をシフトする必要がある
このようD
に、基本型配列に渡されると、型キャストは実際にそのオフセットを計算しB2
、メモリ内で有効なように見えるものを指します。
b2s[1] = &d;
これにはのD
代わりにvtableがあるためB2
、すべての仮想呼び出しは透過的に機能します。
これで、最終的に型キャストと具体例の分析に戻ることができます。
stdout出力から、次のことがわかります。
&d 0x7fffffffc930
b2s[1] 0x7fffffffc940
したがって、static_cast
そこで行われる暗黙的にD
、0x7fffffffc930にある完全なデータ構造から0x7fffffffc940にあるB2
類似のデータ構造までのオフセットを正しく計算しました。また、0x7fffffffc930と0x7fffffffc940の間にあるのは、おそらくB1
データとvtableであると推測しています。
次に、ダウンキャストセクションで、無効なものがどのように失敗するか、およびその理由を簡単に理解できるようになりました。
static_cast<D*>(b2s[0]) 0x7fffffffc910
:コンパイラーは、コンパイル時にバイトで0x10を増やし、a B2
からコンテナーに移動しようとしましたD
しかしb2s[0]
、D
ではなかったため、未定義のメモリ領域をポイントします。
分解は次のとおりです。
49 dp = static_cast<D*>(b2s[0]);
0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax
0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433>
0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax
0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438>
0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
したがって、GCCは次のことを行います。
D
して、存在しないに到達しますdynamic_cast<D*>(b2s[0]) 0
:C ++は、キャストが無効であることを実際に検出して返しましたnullptr
。
コンパイル時にこれを行う方法はありません。逆アセンブリから確認します。
59 dp = dynamic_cast<D*>(b2s[0]);
0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax
0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax
0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744>
0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx
0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D>
0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2>
0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi
0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt>
0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749>
0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax
0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
最初にNULLチェックがあり、einputがNULLの場合はNULLを返します。
それ以外の場合は、RDX、RSI、RDIにいくつかの引数を設定し、を呼び出します__dynamic_cast
。
現在、これをさらに分析する忍耐力はありませんが、他の人が言ったように、これが機能する唯一の方法は__dynamic_cast
、クラス階層を表す追加のRTTIインメモリデータ構造にアクセスすることです。
したがってB2
、そのテーブルのエントリから開始し、次にこのクラス階層をたどって、D
からの型キャストのvtableを見つける必要がありb2s[0]
ます。
これが、キャストの再解釈が高価になる可能性がある理由です!以下は、複雑なプロジェクトでa dynamic_cast
をa static_cast
に変換する1つのライナーパッチがランタイムを33%削減した例です。。
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
これは盲目的に私たちを信じているだけです:D
atアドレスb2s[1]
があり、コンパイラーはオフセット計算を行いません。
しかし、これは間違っています。Dは実際には0x7fffffffc930にあるため、0x7fffffffc940にあるのはD内のB2に似た構造です。だからゴミがアクセスされます。
これ-O0
は、値を移動するだけの恐ろしいアセンブリから確認できます。
70 dp = reinterpret_cast<D*>(b2s[1]);
0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax
0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
関連する質問:
Ubuntu 18.04 amd64、GCC 7.4.0でテスト済み。