static_cast、dynamic_cast、const_cast、およびreinterpret_castはいつ使用する必要がありますか?


2496

の適切な用途は何ですか:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Cスタイルのキャスト (type)value
  • 関数スタイルのキャスト type(value)

どのように特定のケースでどちらを使用するかをどのように決定しますか



3
さまざまな種類のキャストを使用するいくつかの有用な具体例については、この他のトピックの同様の質問の最初の回答を確認できます。
TeaMonkie、2017

2
上記の質問には本当に良い答えがあります。しかし、ここでもう1つ、@ e.James氏に述べたいと思います。「これらの新しいc ++キャスト演算子で実行できることは何もありません。cスタイルキャストではできません。これらは、コードの読みやすさを向上させるために多少追加されます。」
BreakBadSP 2018年

@BreakBadSP新しいキャストは、コードを読みやすくするためだけのものではありません。それらは、値の代わりにconstをキャストしたり、ポインターをキャストしたりするなど、危険なことを行うのを難しくするためにあります。static_castは、acスタイルのキャストよりも危険なことを行う可能性がはるかに少ないです!
FourtyTwo

@FourtyTwoが合意
BreakBadSP

回答:


2571

static_cast使用しようとする最初のキャストです。これは、型間の暗黙的な変換(intto floatやへのポインターなど)などを行い、void*明示的な変換関数(または暗黙的な変換関数)を呼び出すこともできます。多くの場合、明示的に述べるstatic_cast必要はありませんが、T(something)構文は同等で(T)somethingあり、避けるべきであることに注意することが重要です(詳細は後で説明します)。T(something, something_else)ただし、A は安全で、コンストラクターの呼び出しが保証されています。

static_cast継承階層をキャストすることもできます。上方向に(基本クラスに向かって)キャストする場合は不要ですが、下方向にキャストする場合は、virtual継承を介してキャストしない限り使用できます。ただし、チェックは行われずstatic_cast、階層を実際にオブジェクトのタイプではないタイプに下げることは未定義の動作です。


const_castconst変数を削除または追加するために使用できます。他の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つの理由にすぎません。


17
dynamic_castはポリモーフィック型専用です。派生クラスにキャストするときにのみ使用する必要があります。static_castは、特にdynamic_castの機能が必要でない限り、最初のオプションです。一般的には、奇跡的な銀の弾丸の「型チェックキャスト」ではありません。
2008

2
正解です。2つのポインタ/参照は自動的に階層をキャストしないので、Base *&にキャストするDerived *&がある場合、階層をキャストするためにstatic_castが必要になる場合があります。私は2分前にそのような(ありふれた、一般的ではない)状況に遭遇しました。;-)
bartgol 2013

5
*「他のC ++キャストでは削除constできません(さえもreinterpret_cast)」...本当に?どうreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))ですか?
user541686 2015年

29
上記で欠けている重要な詳細は、static_reinterpret_castと比較して、dynamic_castの実行時のパフォーマンスが低下することです。これは、たとえばリアルタイムソフトウェアでは重要です。
jfritz42 2015

5
これreinterpret_castは、APIの不透明なデータ型のセットを処理する際によく使用される武器であることに言及する価値があるかもしれません
クラススケルトン

333

dynamic_cast継承階層内のポインター/参照の変換に使用します。

static_cast通常の型変換に使用します。

reinterpret_castビットパターンの低レベルの再解釈に使用します。細心の注意を払って使用してください。

const_cast離れてキャストするために使用しますconst/volatile。const-incorrect APIを使用してスタックしない限り、これを回避してください。


2
dynamic_castには注意してください。これはRTTIに依存しており、これは共有ライブラリの境界を越えて期待どおりに機能しません。単純に実行可能ファイルと共有ライブラリを独立してビルドするため、異なるビルド間でRTTIを同期する標準化された方法はありません。このため、QtライブラリにはタイプのチェックにQObjectタイプ情報を使用するqobject_cast <>が存在します。
user3150128 2018年

198

(多くの理論的および概念的な説明が上に与えられました)

以下は、いくつかのある具体例私が使用し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);
}

31
他の回答のいくつかの理論は良いですが、それでも混乱します。他の回答を読んだ後にこれらの例を見ると、それらはすべて意味をなさないのです。それは例がなければ、私はまだ確信が持てませんでしたが、彼らとともに、私は今、他の答えが何を意味するかについて確信しています。
Solx 2014

1
reinterpret_castの最後の使用について:これは使用と同じではありませんstatic_cast<char*>(&val)か?
Lorenzo Belli 2016年

3
@LorenzoBelliもちろん違います。やってみましたか?後者は有効なC ++ではなく、コンパイルをブロックします。static_cast変換が定義されているタイプ間、継承による可視関係、またはto / fromの間でのみ機能しvoid *ます。他のすべてのために、他のキャストがあります。reinterpret cast任意のchar *型へのアクセスは、任意のオブジェクトの表現の読み取りを許可されます-そして、そのキーワードが役立つ唯一のケースの1つであり、実装/未定義の動作の蔓延するジェネレータではありません。ただし、これは「通常の」変換とは見なされないため、(通常)非常に保守的な変換では許可されていませんstatic_cast
underscore_d

2
データベースなどのシステムソフトウェアを使用している場合、reinterpret_castはかなり一般的です。ほとんどの場合、ページに格納されているデータ型がわからない独自のページマネージャを作成し、ボイドポインタを返すだけです。キャストを再解釈して好きなように推測するのは、より高いレベルまでです。
Sohaib

1
const_castの例は、未定義の動作を示しています。constとして宣言された変数はdeconst-edできません。ただし、const参照を取得する関数に渡される、非constとして宣言された変数は、その関数ではUBでなくてもdeconstすることができます。
ヨハンGerell

99

内部について少し知っていれば役立つかもしれません...

static_cast

  • C ++コンパイラーは、floatなどのスケーラー型からintへの変換方法をすでに知っています。static_castそれらに使用します。
  • あなたがタイプへ変換するコンパイラを依頼するときABstatic_cast呼び出しBのコンストラクタは、合格Aのparamとして。または、A変換演算子(つまりA::operator B())を含めることもできます。そのBようなコンストラクタがない場合、またはA変換演算子がない場合は、コンパイル時エラーが発生します。
  • AとBが継承階層(またはvoid)にある場合、キャストfrom A*B*常に成功します。そうでない場合、コンパイルエラーが発生します。
  • 落とし穴:ベースポインターを派生ポインターにキャストしても、実際のオブジェクトが実際に派生型でない場合、エラーは発生しません。実行時にポインタが不良になり、セグメンテーション違反が発生する可能性が非常に高くなります。同じことがのために行くA&B&
  • Gotcha:DerivedからBaseへのキャスト、またはその逆で新しいコピーが作成されます!C#/ Javaから来た人にとって、結果は基本的にDerivedから作成された切り取られたオブジェクトであるため、これは大きな驚きになる可能性があります。

dynamic_cast

  • dynamic_castは、ランタイムタイプ情報を使用して、キャストが有効かどうかを判断します。たとえば、ポインタが実際に派生型でない場合、失敗(Base*)する(Derived*)ことがあります。
  • つまり、dynamic_castはstatic_castに比べて非常に高価です。
  • 以下のためA*B*、キャストが無効である場合、その後はdynamic_castはnullptrを返します。
  • 以下のためA&B&キャストが無効である場合、その後はdynamic_castはbad_cast例外がスローされます。
  • 他のキャストとは異なり、ランタイムのオーバーヘッドがあります。

const_cast

  • static_castは非constからconstを実行できますが、他の方法では回避できません。const_castは両方の方法で実行できます。
  • これが便利な1つの例はset<T>、キーを変更しないことを確認するために、constとして要素のみを返すようなコンテナを反復処理することです。ただし、目的がオブジェクトの非キーメンバーを変更することである場合は、問題ありません。const_castを使用してconstnessを削除できます。
  • もう1つの例は、T& SomeClass::foo()と同様に実装する場合ですconst T& SomeClass::foo() const。コードの重複を回避するために、const_castを適用して、ある関数の戻り値を別の関数から返すことができます。

reinterpret_cast

  • これは基本的に、このメモリ位置でこれらのバイトを取得し、それを所定のオブジェクトと見なすことを示しています。
  • たとえば、4バイトのfloatを4バイトのintにロードして、floatのビットがどのように見えるかを確認できます。
  • 明らかに、データがそのタイプに対して正しくない場合、segfaultが発生する可能性があります。
  • このキャストには実行時のオーバーヘッドはありません。

変換演算子の情報を追加しましたが、他にも修正が必要なものがいくつかあり、これを更新しすぎるのは快適ではありません。アイテムは次のとおりです。1 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することは、論理的な定数を実装するためのより良い選択かもしれません。
エイドリアン

1
@エイドリアンあなたはすべての点で正しいです。答えは多かれ少なかれ初心者レベルの人々のために書かれており、私はmutable、クロスキャスティングなどに伴う他のすべての複雑化で彼らを圧倒したくありませんでした
Shital Shah

16

これは、あなたの質問に答えますか?

私はを使用reinterpret_castしたことがなく、それを必要とするケースに出くわすことは、悪いデザインの匂いではないかと思います。私が取り組んでいるコードベースでdynamic_castは、よく使われています。との違い static_castは、dynamic_castランタイムチェックが(安全)かそうでない(オーバーヘッドが多い)かを確認することです(msdnを参照)。


3
私はreintrepret_castを1つの目的で使用しました-doubleからビットを取り出す(私のプラットフォームではlong longと同じサイズ)。
ジョシュア

2
COMオブジェクトを操作する場合などには、reinterpret_castが必要です。CoCreateInstance()には、タイプvoid **(最後のパラメーター)の出力パラメーターがあり、たとえば「INetFwPolicy2 * pNetFwPolicy2」として宣言されたポインターを渡します。そのためには、reinterpret_cast <void **>(&pNetFwPolicy2)のようなものを書く必要があります。
セルジュ・ロガッチ、2015年

1
おそらく別の方法がありますが、私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); }
James Matta

私はこれまで使用reinterpret_castしたことがありません。その使用方法はそれほど多くありません。
ピカクジラの魔法使い

個人的にはreinterpret_cast、1つの理由で使用されるのを見たことがあります。データベースの「blob」データ型に保存された生のオブジェクトデータを確認しました。その後、データベースからデータを取得すると、reinterpret_castこの生のデータをオブジェクトに変換するために使用されます。
ImaginaryHuman072889

15

これまでの他の回答に加えて、これ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)か?
jp48

9

他の回答では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スタイルのキャストは、名前付きの変換演算子よりもはるかに危険です。これは、大規模なプログラムでは表記法を見つけるのが難しく、プログラマーが意図する変換の種類が明確でないためです。


5

理解するために、以下のコードスニペットを考えてみましょう。

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は、値の変換を行うCスタイルのキャストに相当するものとして、またはクラスからスーパークラスにポインターを明示的にアップキャストする必要がある場合に使用します。
  • const_castを使用してconst修飾子を削除します。
  • reinterpret_castを使用して、整数およびその他のポインター型との間のポインター型の安全でない変換を行います。これを使用するのは、私たちが何をしているかがわかっていて、エイリアスの問題を理解している場合だけです。

3

static_castダウンキャスト/アップキャストのvs dynamic_castvs 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互換性のあるメモリ構造を内部に含んでいることです。B1B2

したがって、重要な結論に到達します。

アップキャストまたはダウンキャストは、コンパイル時に既知の値だけポインタ値をシフトする必要がある

このよう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は次のことを行います。

    • ポインタがNULLかどうかをチェックし、そうであればNULLを返す
    • それ以外の場合は、0x10を減算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これは盲目的に私たちを信じているだけです:Datアドレス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でテスト済み。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.