いつreinterpret_castを使用するのですか?


459

reinterpret_castvs の適用性に少し混乱していstatic_castます。私が読んだことから、一般的なルールは、コンパイル時に型を解釈できるときに静的キャストを使用することstaticです。これは、C ++コンパイラが暗黙的なキャストのために内部的に使用するキャストでもあります。

reinterpret_castsは次の2つのシナリオに適用されます。

  • 整数型からポインタ型へ、またはその逆への変換
  • あるポインタ型を別のポインタ型に変換します。私が得る一般的な考えは、これは移植性がなく、避けるべきであるということです。

私が少し混乱しているのは必要な使用法の1つです。CからC ++を呼び出しています。CコードはC ++オブジェクトを保持する必要があるため、基本的にはを保持していvoid*ます。とvoid *クラス型の間の変換にはどのキャストを使用する必要がありますか?

私は両方の使い方を見ているstatic_castreinterpret_cast?私が読んでいるものからstatic、キャストがコンパイル時に発生する可能性があるので、それはより良いように見えますか?reinterpret_castあるポインタ型から別のポインタ型に変換するために使用すると書かれていますが?


9
reinterpret_cast実行時には発生しません。どちらもコンパイル時のステートメントです。en.cppreference.com/w/cpp/language/reinterpret_castから:「static_castとは異なり、const_castと同様に、reinterpret_cast式はCPU命令にコンパイルされません。これは、コンパイラにビットのシーケンスを処理するよう指示する純粋なコンパイラディレクティブですタイプがnew_typeであるかのように式の(オブジェクト表現)。
クリスルエンゴ2017

@HeretoLearn、*。cおよび* .cppファイルから関連するコードを追加することは可能ですか?質問の説明を改善できると思います。
OrenIshShalom 2017年

回答:


442

C ++標準では、次のことが保証されています。

static_castポインタを使用しvoid*て、アドレスを保持します。つまり、以下でabcすべてが同じアドレスを指しています。

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_castあなただけは別のタイプへのポインタをキャストし、場合ことを保証して、reinterpret_castそれが元の型に、あなたは元の値を取得します。したがって、次のようになります。

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

aおよびc同じ値が含まれていますが、値bは指定されていません。(実際には、通常、aおよびと同じアドレスが含まcれますが、これは標準では指定されていません。また、より複雑なメモリシステムを備えたマシンでは当てはまらない場合があります。)

へのキャストおよびからのキャストにはvoid*static_castを推奨します。


18
「b」が定義されていないという事実が好きです。それはあなたが愚かなことをするのをやめます。何かを別のポインタ型にキャストすると、問題が発生し、それに依存できないという事実がより注意深くなります。上記のstatic_cast <>を使用していた場合、とにかく 'b'の用途は何ですか?
マーティンヨーク

3
reinterpret_cast <>でも同じビットパターンが保証されると思いました。(これは別の型への有効なポインターと同じではありません)。
マーティンヨーク

37
を使用する場合b、C ++ 11ではの値が指定されなくなりましたreinterpret_cast。また、C ++ 03では、int*toのキャストvoid*は禁止されていますreinterpret_cast(ただし、コンパイラはそれを実装しておらず、実際的ではなかったため、C ++ 11に変更されました)。
ヨハネスシャウブ-litb '28

55
これは、「reinterpret_castをいつ使用するか」という質問には実際には答えません。
einpoklum

6
@LokiAstari私は不特定であってもあなたが愚かなことをするのを止めるものではないと思います。それが特定されていないことを覚えているときだけそれはあなたを止めます。大きな違い。個人的に私は不特定を好まない。覚えるには多すぎる。
Helin Wang 2017

158

reinterpret_cast必要な場合の1つは、不透明なデータ型とのインターフェースです。これは、プログラマーが制御できないベンダーAPIで頻繁に発生します。これは、ベンダーが任意のグローバルデータを格納および取得するためのAPIを提供する不自然な例です。

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

このAPIを使用するには、プログラマーは自分のデータをVendorGlobalUserData再度キャストする必要があります。 static_cast動作しません、使用する必要がありますreinterpret_cast

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

以下は、サンプルAPIの意図的な実装です。

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

7
うん、それは私が考えることができるreinterpret_castの唯一の意味のある使用についてです。
2009

8
これは遅い質問かもしれませんが、なぜベンダーAPIがそれを使用void*しないのですか?
Xeo

19
@Xeoコンパイル時に型チェックが(一部)失われるため、void *を使用しません。
jesup 2014

4
「不透明な」データ型の実用的な使用例は、APIをCに公開したいが、C ++で実装を記述したい場合です。ICUは、いくつかの場所でこれを行うライブラリーの例です。たとえば、なりすましチェッカーAPIでは、が空の構造体USpoofChecker*である型のポインターを扱いますUSpoofChecker。ただし、内部USpoofChecker*では、を渡すと常にreinterpret_cast内部C ++型になります。
sffc 2017年

@sffc C構造体型をユーザーに公開しないのはなぜですか?
Gupta

101

短い答え: 何のreinterpret_cast略かわからない場合は、使用しないでください。あなたが将来それを必要とするならば、あなたは知っているでしょう。

完全な答え:

基本的な数値タイプを考えてみましょう。

あなたは、例えば変換するときint(12)unsigned float (12.0f)、あなたのプロセッサの両方の番号が異なるビット表現を持っているとして、いくつかの計算を呼び出す必要があります。これは何static_cast意味するです。

一方、reinterpret_castCPUを呼び出すと、計算は呼び出されません。メモリ内のビットのセットを、別のタイプのビットのように扱うだけです。に変換するint*float*このキーワードてに、新しい値(ポインターの逆参照後)は、数学的な意味で古い値とは関係ありません。

例:reinterpret_castバイト順序(エンディアン)の1つの理由により移植性がないことは事実です。しかし、多くの場合、これが驚くほど使用する最良の理由です。例を想像してみてください。ファイルからバイナリの32ビット数を読み取る必要があり、ビッグエンディアンであることがわかります。コードはジェネリックである必要があり、ビッグエンディアン(たとえば、一部のARM)およびリトルエンディアン(たとえば、x86)システムで適切に動作します。したがって、バイト順序を確認する必要があります。コンパイル時によく知られているのでconstexpr、関数を記述できます。これを実現する関数を記述できます。

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

説明:xメモリ内ののバイナリ表現は、0000'0000'0000'0001(ビッグ)または0000'0001'0000'0000(リトルエンディアン)の可能性があります。再解釈キャストした後、pポインターの下のバイトはそれぞれ0000'0000またはになり0000'0001ます。静的キャスティングを使用する場合、それは常に0000'0001、エンディアンが使用されているかどうかに関係なく、。

編集:

最初のバージョンでは、サンプル関数is_little_endianをにしましたconstexpr。最新のgcc(8.3.0)では問題なくコンパイルされますが、標準ではそれは違法であると述べています。clangコンパイラはそれをコンパイルすることを拒否します(これは正しいです)。


1
いい例です!私は、uint16_tをshortに、unint8_tをunsigned charに置き換えて、人間にとってわかりやすくしました。
JanTuroň2018

@JanTuroň真shortです。メモリに16ビットを使用するとは想定できません。修正。
jaskmar 2018

1
例は間違っています。reinterpret_castはconstexpr関数では許可されません
Michael Veksler

1
まず、このコードは最新のclang(7.0.0)とgcc(8.2.0)の両方で拒否されます。残念ながら、私は正式な言語の制限を見つけられませんでした。私が見つけたのは、social.msdn.microsoft.com / Forums / vstudio / en
Michael Veksler

2
より具体的には、en.cppreference.com / w / cpp / language / constant_expression(項目16)には、reinterpret_castは定数式では使用できないことが明記されています。また、reinterpret_cast を明示的に除外するgithub.com/cplusplus/draft/blob/master/papers/N3797.pdf(5.19定数式)pages125-126 も参照してください。次に7.1.5 constexpr指定子の項目5(146ページ)*テンプレートではなく、デフォルトではないconstexpr関数の場合...コア定数式(5.19 )、プログラムの形式が
正しく

20

の意味はreinterpret_cast、C ++標準では定義されていません。したがって、理論的にreinterpret_castは、プログラムをクラッシュさせる可能性があります。実際には、コンパイラーは期待どおりのことをしようとします。つまり、渡されているもののビットを、キャスト先の型であるかのように解釈します。使用するコンパイラーをどのように使用するかがわかっていれば使用reinterpret_cast できますが、移植可能であると言うのは嘘です。

あなたが説明する場合、そしてあなたが考えるかもしれないほとんどすべての場合についてreinterpret_cast、あなたはstatic_cast代わりにまたは他のいくつかの代替手段を使うことができます。他のものの間で、規格はあなたが何を期待できるかについてこれを言っているstatic_cast(§5.2.9):

タイプ「cv voidへのポインター」の右辺値は、オブジェクトタイプへのポインターに明示的に変換できます。オブジェクトへのタイプポインタの値は、「cv voidへのポインタ」に変換され、元のポインタタイプに戻ると、元の値になります。

したがって、あなたのユースケースでは、標準化委員会があなたが使用することを意図していたことはかなり明白に思われますstatic_cast


5
プログラムを完全にクラッシュさせないでください。標準では、reinterpret_castに関するいくつかの保証が提供されています。人々が期待するほど多くはありません。
jalf

1
あなたがそれを適切に使用するならそうではありません。つまり、AからBからAへのreinterpret_castは完全に安全で、明確に定義されています。しかし、Bの値は指定されていません。そうだとすると、それに依存すると、悪いことが発生する可能性があります。しかし、キャストがそれ自体で十分安全であるのは、規格が許可する方法でのみ使用した場合です。;)
jalf

55
lol、私はreinterpret_crashが実際にプログラムをクラッシュさせるのではないかと思います。しかし、reinterpret_castはしません。;)
2009

5
<irony>コンパイラーで試したところ、どういうわけかコンパイルできませんreinterpret_crashでした。コンパイラのバグが原因で、再解釈プログラムがクラッシュすることはありません。私は
できるだけ早く

18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }

12

reinterpret_castの用途の1つは、ビット単位の演算を(IEEE 754)floatに適用する場合です。この1つの例は、高速逆平方根トリックでした。

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

浮動小数点のバイナリ表現を整数として扱い、右にシフトして定数から減算することで、指数を半分にして無効にします。フロートに変換して戻した後、この近似をより正確にするために、ニュートンラフソン反復が行われます。

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

これは元々Cで記述されていたため、Cキャストを使用していますが、類似のC ++キャストはreinterpret_castです。


1
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))- ideone.com/6S4ijc
Orwellophile

1
標準では、これは未定義の動作であると記載されています:en.cppreference.com/w/cpp/language/reinterpret_cast(「type aliasing」の下)
Cris Luengo

@CrisLuengoすべてreinterpret_castをに置き換えmemcpyても、まだUBですか?
サンソーン

@sandthorn:これは標準によるとUBですが、ご使用のアーキテクチャで機能する場合は、心配する必要はありません。このトリックは、Intelアーキテクチャ用のどのコンパイラでも問題ないと思います。他のアーキテクチャでは意図したとおりに機能しない(またはクラッシュする)こともありません。たとえば、floatとlongが別々のメモリコンパートメントに格納されている可能性があります(そのようなアーキテクチャを知っているわけではなく、単なる引数です...)。 。memcpy間違いなく合法になります。
クリスルエンゴ2018


2
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

テンプレートを使用して、簡単な安全なキャストをまとめて書きました。このソリューションは、ポインターを関数にキャストすることを保証しないことに注意してください。


1
何?なぜわざわざ?これはまさにですreinterpret_castすでにこのような状況ではありません:「。オブジェクトポインタが明示的に異なるタイプのオブジェクトポインタに変換することができます[72] prvalue vオブジェクトのポインタ型のは、オブジェクトポインタ型に変換される『へのポインタCV T』、結果はstatic_cast<cv T*>(static_cast<cv void*>(v))です。」-N3797。
underscore_d

c++2003標準に関しては、私はそれを見つけることができませんreinterpret_caststatic_cast<cv T*>(static_cast<cv void*>(v))
Sasha Zezulinsky

1
はい、そうですが、13年前のバージョンについては気にしていませんし、ほとんどのコーダーが(おそらく)回避できるかどうかも気にしません。回答とコメントは、特に明記されていない限り、実際に利用可能な最新の標準を反映する必要があります。とにかく、委員会は2003年以降これを明示的に追加する必要を感じたと思います。(IIRCのため、C ++ 11でも同じでした)
underscore_d

以前C++03だったC++98。多くのプロジェクトで、移植可能なCではなく古いC ++が使用されていました。場合によっては、移植性に注意する必要があります。たとえば、Solaris、AIX、HPUX、Windowsで同じコードをサポートする必要があります。コンパイラーの依存関係と移植性に関しては注意が必要です。したがって、移植性の地獄を導入する良い例はreinterpret_cast、コードでaを使用することです
Sasha Zezulinsky

繰り返しますが、私のように、最新かつ最高のバージョンの言語でうまく機能するプラットフォームのみに制限されて満足している場合、あなたの異議は議論の余地があります。
underscore_d

1

最初に、intのような特定のタイプのデータがあります。

int x = 0x7fffffff://==nan in binary representation

次に、floatなどの他の型と同じ変数にアクセスする必要があります。

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

または

float y = *(float*)&(x);

//this could be used in c and cpp

簡単:同じメモリが別のタイプとして使用されていることを意味します。したがって、上記のようなint型としての浮動小数点のバイナリ表現を浮動小数点に変換できます。たとえば、0x80000000は-0です(仮数と指数はnullですが、符号msbは1です。これは、doubleとlong doubleでも機能します。

最適化:reinterpret_castは多くのコンパイラーで最適化されると思いますが、cキャスティングはポインター演算によって行われます(値をメモリにコピーする必要があるため、ポインターがcpuレジスターをポイントできませんでした)。

注:どちらの場合も、キャストする前にキャストした値を変数に保存する必要があります。このマクロは次のことに役立ちます。

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

「同じメモリが別のタイプとして使用されることを意味します」というのは事実ですが、特定のタイプのペアに限定されています。あなたの例reinterpret_castでは、intto float&は未定義の動作です。
jaskmar

1

使用する理由の1つreinterpret_castは、基本クラスにはvtableがないが、派生クラスにはある場合です。その場合、static_cast及びreinterpret_cast異なるポインタ値をもたらすであろう(これは言及非定型場合である上方jalf)。免責事項として、私はこれが標準の一部であるとは述べていませんが、いくつかの広範なコンパイラの実装です。

例として、以下のコードを見てください。

#include <cstdio>

class A {
public:
    int i;
};

class B : public A {
public:
    virtual void func() {  }
};

int main()
{
    B b;
    const A* a = static_cast<A*>(&b);
    const A* ar = reinterpret_cast<A*>(&b);

    printf("&b = %p\n", &b);
    printf(" a = %p\n", a);
    printf("ar = %p\n", ar);
    printf("difference = %ld\n", (long int)(a - ar));

    return 0;
}

これは次のようなものを出力します:

&b = 0x7ffe10e68b38
a = 0x7ffe10e68b40
ar = 0x7ffe10e68b38
差= 2

私が試したすべてのコンパイラ(MSVC 2015&2017、clang 8.0.0、gcc 9.2、icc 19.0.1- 最後の3についてはgodboltを参照)で、2の結果とstatic_cast異なる結果が得られましreinterpret_castた(MSVCの場合は4)。違いについて警告する唯一のコンパイラはclangで、次のものを備えています。

17:16:警告:クラス 'B *'からゼロ以外のオフセット 'A *'でのベースへの 'reinterpret_cast'の動作は 'static_cast'とは異なります[-Wreinterpret-base-class]
const A * ar = reinterpret_cast (&b) ;
^ ~~~~~~~~~~~~~~~~~~~~~~~
17:16:注:
const A * ar = reinterpret_cast (&b)をアップキャストしている間、 'static_cast'を使用してポインタを正しく調整します;
^ ~~~~~~~~~~~~~~~
static_cast

最後の注意点の1つは、基本クラスにデータメンバー(例int i;:)がない場合、clang、gcc、およびiccはfor reinterpret_castと同じアドレスを返しますがstatic_cast、MSVCはまだ返しません。


1

以下は、Avi Ginsburgのプログラムの変形でreinterpret_cast、Chris Luengo、flodin、およびcmdLPによって言及されたプロパティを明確に示しています。コンパイラは、ポイントされたメモリの場所を新しいタイプのオブジェクトであるかのように扱います。

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;

class A
{
public:
    int i;
};

class B : public A
{
public:
    virtual void f() {}
};

int main()
{
    string s;
    B b;
    b.i = 0;
    A* as = static_cast<A*>(&b);
    A* ar = reinterpret_cast<A*>(&b);
    B* c = reinterpret_cast<B*>(ar);

    cout << "as->i = " << hex << setfill('0')  << as->i << "\n";
    cout << "ar->i = " << ar->i << "\n";
    cout << "b.i   = " << b.i << "\n";
    cout << "c->i  = " << c->i << "\n";
    cout << "\n";
    cout << "&(as->i) = " << &(as->i) << "\n";
    cout << "&(ar->i) = " << &(ar->i) << "\n";
    cout << "&(b.i) = " << &(b.i) << "\n";
    cout << "&(c->i) = " << &(c->i) << "\n";
    cout << "\n";
    cout << "&b = " << &b << "\n";
    cout << "as = " << as << "\n";
    cout << "ar = " << ar << "\n";
    cout << "c  = " << c  << "\n";

    cout << "Press ENTER to exit.\n";
    getline(cin,s);
}

これは次のような出力になります:

as->i = 0
ar->i = 50ee64
b.i   = 0
c->i  = 0

&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978

&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c  = 00EFF974
Press ENTER to exit.

Bオブジェクトは、最初にB固有のデータとしてメモリに組み込まれ、次に埋め込まれたAオブジェクトが続くことがわかります。static_cast正しく埋め込まれたAのオブジェクトのアドレスを返し、によって作成されたポインタstatic_cast正しくは、データフィールドの値を与えます。reinterpret_cast御馳走によって生成されたポインタbのメモリ位置をプレーンAオブジェクトであるかのようにます。したがって、ポインタがデータフィールドを取得しようとすると、このフィールドの内容であるかのようにB固有のデータを返します。

の使用法の1つreinterpret_castは、ポインターを符号なし整数に変換することです(ポインターと符号なし整数が同じサイズの場合)。

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);


-6

簡単な回答:static_castコンパイルする場合は使用し、それ以外の場合はに頼ってくださいreinterpret_cast


-16

FAQを読んでください!C ++データをCで保持することは危険な場合があります。

C ++では、オブジェクトへのポインターvoid *はキャストなしでに変換できます。しかし、その逆は正しくありません。static_cast元のポインタを元に戻すには、が必要です。

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