28

注:この質問は、より焦点を絞って読みやすくするために名前が変更され、縮小されています。コメントのほとんどは古いテキストを参照しています。


標準によれば、異なるタイプのオブジェクトは同じメモリ位置を共有しない場合があります。したがって、これは合法ではありません。

std::array<short, 4> shorts;
int* i = reinterpret_cast<int*>(shorts.data()); // Not OK

ただし、この標準では、この規則の例外が許可されています。任意のオブジェクトには、charまたはへのポインタを介してアクセスできunsigned charます。

int i = 0;
char * c = reinterpret_cast<char*>(&i); // OK

しかし、これが逆に許可されているかどうかは私にはわかりません。例えば:

char * c = read_socket(...);
unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
4

3 に答える 3

23

ポインタ変換が含まれるため、コードの一部に問題があります。これらのインスタンスには、関連するタイプが標準レイアウトであるためreinterpret_cast<T*>(e)のセマンティクスがあることに注意してください。(実際、ストレージを扱うときは常にviaを使用するstatic_cast<T*>(static_cast<void*>(e))ことをお勧めします。)static_castcv void*

標準をよく読むと、ポインタの変換中に実際に関係T*するオブジェクトが実際に存在すると想定されていることがわかりますT*。これは、型の些細なことのおかげで「不正行為」をしている場合でも、スニペットの一部で実行するのは困難です。関与します(これについては後で詳しく説明します)。しかし、それは要点を超えているでしょう...

エイリアシングはポインタ変換に関するものではありません。これは、3.10の左辺値と右辺値[basic.lval]から、一般に「厳密なエイリアシング」ルールと呼ばれるルールの概要を示すC++11テキストです。

10プログラムが、次のいずれかのタイプ以外のglvalueを介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的タイプのcv修飾バージョン。
  • オブジェクトの動的タイプに類似したタイプ(4.4で定義)、
  • オブジェクトの動的型に対応する符号付きまたは符号なしの型である型、
  • オブジェクトの動的型のcv修飾バージョンに対応する符号付きまたは符号なし型である型。
  • 要素または非静的データメンバー(再帰的に、サブアグリゲートまたは含まれるユニオンの要素または非静的データメンバーを含む)の中に前述のタイプの1つを含む集合体または共用体タイプ。
  • オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型、
  • char型またはunsignedchar型。

(これは、C ++ 03の同じ節と副節の段落15ですが、テキストにいくつかの小さな変更があります。たとえば、後者はC ++ 11の概念であるため、「glvalue」の代わりに「lvalue」が使用されます。)

magic_cast<T*>(p)これらの規則に照らして、実装が「どういうわけか」ポインタを別のポインタ型に変換するものを提供すると仮定しましょう。通常、これでありreinterpret_cast、場合によっては不特定の結果が得られますが、前に説明したように、これは標準レイアウトタイプへのポインタには当てはまりません。次に、すべてのスニペットが正しい(で置き換えreinterpret_castられるmagic_cast)ことは明らかです。これは、の結果にglvalueがまったく含まれていないためですmagic_cast

これは間違って使用しているように見えるスニペットですがmagic_cast、私が主張するのは正しいです:

// assume constexpr max
constexpr auto alignment = max(alignof(int), alignof(short));
alignas(alignment) char c[sizeof(int)];
// I'm assuming here that the OP really meant to use &c and not c
// this is, however, inconsequential
auto p = magic_cast<int*>(&c);
*p = 42;
*magic_cast<short*>(p) = 42;

私の推論を正当化するために、この表面的に異なるスニペットを想定します。

// alignment same as before
alignas(alignment) char c[sizeof(int)];

auto p = magic_cast<int*>(&c);
// end lifetime of c
c.~decltype(c)();
// reuse storage to construct new int object
new (&c) int;

*p = 42;

auto q = magic_cast<short*>(p);
// end lifetime of int object
p->~decltype(0)();
// reuse storage again
new (p) short;

*q = 42;

このスニペットは慎重に作成されています。特に、3.8オブジェクトの存続期間[basic.life]のパラグラフ5に規定されている規則のために破壊されたとしても、new (&c) int;私は使用を許可されています。同じのパラグラフ6は、ストレージへの参照に非常によく似たルールを示し、パラグラフ7は、ストレージが再利用されるとオブジェクトを参照するために使用された変数、ポインター、および参照に何が起こるかを説明します-まとめて3.8/5と呼びます- 7。&cc

この場合&c、(暗黙的に)に変換されますvoid*。これは、まだ再利用されていないストレージへのポインターの正しい使用法の1つです。同様に、新しいものが構築される前pから取得されます。その定義は、実装の魔法の深さに応じて、破壊後に移動する可能性がありますが、構築後ではありません。段落7が適用され、これは許可された状況の1つではありません。オブジェクトの構築は、ストレージへのポインターになることにも依存しています。&cintcintshortp

intとは些細なタイプなので、shortデストラクタへの明示的な呼び出しを使用する必要はありません。コンストラクターへの明示的な呼び出しも必要ありません(つまり、で宣言された通常の標準配置への呼び出し<new>)。3.8からオブジェクトの存続期間[basic.life]:

1 [...]タイプTのオブジェクトの存続期間は、次の場合に始まります。

  • タイプTの適切な配置とサイズのストレージが取得され、
  • オブジェクトに自明でない初期化がある場合、その初期化は完了です。

タイプTのオブジェクトの存続期間は、次の場合に終了します。

  • Tが自明でないデストラクタ(12.4)を持つクラス型の場合、デストラクタ呼び出しが開始されます。
  • オブジェクトが占有するストレージは、再利用または解放されます。

qこれは、中間変数を折りたたんだ後、元のスニペットになるようにコードを書き直すことができることを意味します。

p折りたたむことはできませんのでご注意ください。つまり、次のことは間違いなく正しくありません。

alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;

intオブジェクトが(自明に)2行目で構築されていると仮定すると、それ&cは再利用されたストレージへのポインターになることを意味する必要があります。したがって、3行目は正しくありません。ただし、3.8 / 5-7が原因であり、厳密に言えばエイリアシングルールが原因ではありません。

それを想定しない場合、2行目エイリアシングルールの違反です。許可された例外の1つではない、char c[sizeof(int)]タイプのglvalueを介して実際にオブジェクトであるものを読み取っています。int比較すると、問題ありません(オブジェクトは3行目に自明に構築されている*magic_cast<unsigned char>(&c) = 42;と想定します)。short

Alfと同様に、ストレージを使用する場合は、新しい標準配置を明示的に使用することをお勧めします。些細なタイプの破棄をスキップすることは問題ありませんが、遭遇*some_magic_pointer = foo;すると、3.8 / 5-7の違反(そのポインターがどれほど魔法のように取得されたとしても)またはエイリアシングルールの違反に直面する可能性が非常に高くなります。これは、新しい式の結果も保存することを意味します。これは、オブジェクトが構築されると、マジックポインターを再利用できない可能性が高いためです。これは、3.8/5-7が原因です。

ただし、オブジェクトのバイトを読み取ること(つまり、charまたはunsigned charを使用することは問題ありません)であり、reinterpret_cast魔法を使用することすらありません。static_castviacv void*は、間違いなくその仕事には問題ありません(ただし、Standardではより適切な表現を使用できると思います)。

于 2012-09-27T07:02:51.263 に答える
6

これも:

// valid: char -> type
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);

それは正しくありません。エイリアシングルールは、異なるタイプの左辺値を介してオブジェクトにアクセスすることが合法/違法である状況を示しています。charタイプまたはのポインタを介して任意のオブジェクトにアクセスできるという特定のルールがあるunsigned charため、最初のケースが正しいです。つまり、A=>Bは必ずしもB=>Aを意味するわけではありません。へのポインタを介してにアクセスすることはできますintが、へのポインタを介してにcharアクセスすることはできません。charint


アルフの利益のために:

プログラムが次のタイプのいずれか以外のglvalueを介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的タイプのcv修飾バージョン。
  • オブジェクトの動的タイプに類似したタイプ(4.4で定義)、
  • オブジェクトの動的型に対応する符号付きまたは符号なしの型である型、
  • オブジェクトの動的型のcv修飾バージョンに対応する符号付きまたは符号なし型である型。
  • 要素または非静的データメンバー(再帰的に、サブアグリゲートまたは含まれるユニオンの要素または非静的データメンバーを含む)の中に前述のタイプの1つを含む集合体または共用体タイプ。
  • オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型、
  • char型またはunsignedchar型。
于 2012-09-27T02:09:46.137 に答える
2

…</p>の有効性について

alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);

reinterpret_castコンパイラーによっては、有用なポインター値を生成するという意味で、それ自体はOKかどうかです。この例では、結果は使用されていません。特に、文字配列にはアクセスされていません。したがって、この例について現状のままで言えることはそれほど多くありません。それは単に依存します。

しかし、エイリアシングルールに触れている拡張バージョンを考えてみましょう。

void foo( char* );

alignas(int) char c[sizeof( int )];

foo( c );
int* p = reinterpret_cast<int*>( c );
cout << *p << endl;

そして、コンパイラが有用なポインタ値を保証する場合のみを考えてみましょう。これは、ポインタを同じバイトのメモリに配置するものです(これがコンパイラに依存する理由は、§5.2.10/7の標準のみです。タイプがアラインメント互換である場合のポインター変換に対してそれを保証し、そうでない場合は「未指定」のままにします(ただし、§5.2.10全体は§9.2/ 18と多少矛盾します)。

さて、規格の§3.10 / 10の解釈の1つ、いわゆる「厳密なエイリアシング」句(ただし、規格では「厳密なエイリアシング」という用語は使用されないことに注意してください)。

プログラムが次のタイプのいずれか以外のglvalueを介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的タイプのcv修飾バージョン。
  • オブジェクトの動的タイプに類似したタイプ(4.4で定義)、
  • オブジェクトの動的型に対応する符号付きまたは符号なしの型である型、
  • オブジェクトの動的型のcv修飾バージョンに対応する符号付きまたは符号なし型である型。
  • 要素または非静的データメンバー(再帰的に、サブアグリゲートまたは含まれるユニオンの要素または非静的データメンバーを含む)の中に前述のタイプの1つを含む集合体または共用体タイプ。
  • オブジェクトの動的型の(おそらくcv修飾された)基本クラス型である型、
  • charまたはunsigned charタイプ。

それ自体が言うように、それはバイトに存在するオブジェクトの動的タイプに関係しているということです。c

その解釈では、オブジェクトがそこに配置されている*p場合は読み取り操作はOKであり、そうでない場合はそうではありません。したがって、この場合、配列はポインターを介してアクセスされます。そして、他の方法が有効であることに疑いの余地はありません。オブジェクトをそれらのバイトに配置したとしても、§3.10 / 10の最後のダッシュまでに、値のシーケンスとしてそのオブジェクトに自由にアクセスできますfoointcharint*foointchar

したがって、この(通常の)解釈でfooは、そこに配置した後、オブジェクトintとしてアクセスできるため、 ;という名前のメモリ領域内charに少なくとも1つのオブジェクトが存在します。としてアクセスできるので、少なくともその1つはそこにも存在します。したがって、オブジェクトにアクセスできないという別の回答でのDavidの主張は、この通常の解釈と互換性がありません。charcintintcharint

Davidの主張は、新しい配置の最も一般的な使用法とも互換性がありません。

他に考えられる解釈については、おそらくデビッドの主張と互換性があるかもしれませんが、まあ、私はそれが理にかなっているとは思いません。

したがって、結論として、Holy Standardに関する限り、T*配列へのポインタをキャストするだけで実際に役立つか、コンパイラに依存しません。また、ポイントされる可能性のある値にアクセスすることは、存在するものに依存しないかどうかに関係なく有効です。特に、次のトラップ表現について考えintてみてください。ビットパターンがたまたまそうだったとしても、それが爆発することは望ましくありません。したがって、安全のために、そこに何が含まれているのか、ビットを知る必要があります。foo上記の呼び出しが示すように、コンパイラは一般にそれを知ることができません。たとえば、g++コンパイラの厳密なアライメントベースのオプティマイザは一般にそれを知ることができません…</ p>

于 2012-09-27T05:46:55.137 に答える