19

C ++ 11標準全体を考慮に入れると、準拠する実装が以下の最初のアサーションに成功するが、後者に失敗する可能性はありますか?

#include <cassert>

int main(int, char**)
{  
    const int I = 5, J = 4, K = 3;
    const int N = I * J * K;

    int arr1d[N] = {0};
    int (&arr3d)[I][J][K] = reinterpret_cast<int (&)[I][J][K]>(arr1d);
    assert(static_cast<void*>(arr1d) ==
           static_cast<void*>(arr3d)); // is this necessary?

    arr3d[3][2][1] = 1;
    assert(arr1d[3 * (J * K) + 2 * K + 1] == 1); // UB?
}

そうでない場合、これは技術的にUBであるかどうか、そして最初のアサーションが削除された場合、その答えは変わりますか(reinterpret_castここでアドレスを保持することが保証されていますか?)?また、形状変更が反対方向(3dから1d)で行われた場合、または6x35アレイから10x21アレイに行われた場合はどうなりますか?

編集:答えが、のせいでこれがUBであるという場合、reinterpret_cast他の厳密に準拠した再形成の方法はありますか(たとえば、static_cast中間体との間でvoid *)?

4

2 に答える 2

26

2021 年 3 月 20 日更新:

これと同じ質問が最近Redditで行われましたが、このエイリアシング ルールが考慮されていないため、私の元の回答には欠陥があることが指摘されました。

プログラムが、次の型のいずれとも似ていない型の glvalue を介してオブジェクトの格納された値にアクセスしようとした場合、動作は未定義です。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的型に対応する符号付きまたは符号なしの型、または
  • char、unsigned char、または std :: byte 型。

類似性のルールの下では、これら 2 つの配列型は上記のいずれのケースでも類似していないため、3D 配列を介して 1D 配列にアクセスすることは技術的に未定義の動作です。(これは間違いなく、実際には、ほとんどのコンパイラ/ターゲットでほぼ確実に機能する状況の 1 つです)

元の回答の参照は、古い C++11 ドラフト標準を参照していることに注意してください

元の答え:

reinterpret_cast参照の

標準では、型の左辺値は、へのポインターがへのポインターになることができるT1場合、reinterpret_castへの参照になることができると述べています(§5.2.10/11):T2T1reinterpret_castT2

型の左辺値式は、reinterpret_cast を使用して型 "pointer to" の式を型 "pointer to" に明示的に変換できる場合T1、型 "reference to" にキャストできます。T2T1T2

したがって、 aint(*)[N]を に変換できるかどうかを判断する必要がありint(*)[I][J][K]ます。

reinterpret_castポインターの

へのポインターは、との両方が標準レイアウト型であり、 (§5.2.10/7)より厳密なアラインメント要件がない場合、へのポインターにするT1ことができます。reinterpret_castT2T1T2T2T1

型「T1 へのポインター」の prvalue v が型「cv T2 へのポインター」に変換されるとき、結果は、とのstatic_cast<cv T2*>(static_cast<cv void*>(v))両方が標準レイアウト型 (3.9) であり、 の整列要件が の整列要件よりも厳密でない場合、またはいずれかのタイプが無効な場合。T1T2T2T1

  1. int[N]とはint[I][J][K]標準レイアウト タイプですか?

    intはスカラー型であり、スカラー型の配列は標準レイアウト型と見なされます(§3.9/9)。

    スカラー型、標準レイアウト クラス型 (条項 9)、そのような型の配列、およびこれらの型の cv 修飾バージョン (3.9.3) は、まとめて標準レイアウト型と呼ばれます。

  2. int[I][J][K]には、より厳密なアラインメント要件はありませんint[N]

    演算子の結果はalignof、完全なオブジェクト型 (§3.11/2) のアラインメント要件を示します。

    演算子の結果はalignof、完全なオブジェクトの場合の型のアラインメント要件を反映しています。

    ここでの 2 つの配列は、他のオブジェクトのサブオブジェクトではないため、完全なオブジェクトです。配列に適用するalignofと、要素タイプのアライメント要件が得られます (§5.3.6/3):

    alignofを配列型に適用すると、結果は要素型の位置合わせになります。

    したがって、両方の配列型のアライメント要件は同じです。

これにより、reinterpret_cast有効で同等のものが次のようになります。

int (&arr3d)[I][J][K] = *reinterpret_cast<int (*)[I][J][K]>(&arr1d);

ここで*、 および&は組み込み演算子であり、次と同等です。

int (&arr3d)[I][J][K] = *static_cast<int (*)[I][J][K]>(static_cast<void*>(&arr1d));

static_cast終えたvoid*

static_casttovoid*は、標準の変換 (§4.10/2) で許可されています。

「cv へのポインター」TT( はオブジェクト型) の prvalue は、「cv void へのポインター」型の prvalue に変換できます。「cv へのポインター」を「cv void へのポインター」に変換した結果は、オブジェクトが型の最も派生したオブジェクト (1.8) であるかのように、T型のオブジェクトが存在する格納場所の先頭を指します(つまり、 、基本クラスのサブオブジェクトではありません)。TT

static_casttoが許可されますint(*)[I][J][K](§5.2.9/13):

「cv1 へのポインター」型の prvalue は、「 voidcv2 へのポインター」型の prvalue に変換できますT。ここTで、 はオブジェクト型であり、cv2 は cv1 と同じ cv-qualification またはそれより大きい cv-qualification です。

だからキャストは大丈夫です!しかし、新しい配列参照を介してオブジェクトにアクセスしても大丈夫でしょうか?

配列要素へのアクセス

配列に対して配列添字を実行するのは(§5.2.1/1)arr3d[E2]と同等です。*((E1)+(E2))次の配列添字について考えてみましょう。

arr3d[3][2][1]

まず、arr3d[3]と同等*((arr3d)+(3))です。左辺値arr3dは、配列からポインターへの変換を受けて、int(*)[2][1]. この変換を行うために、基になる配列が正しい型でなければならないという要件はありません。次に、ポインター値にアクセスし (これは §3.10 で問題ありません)、値 3 がそれに追加されます。このポインター演算も問題ありません (§5.7/5):

ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。

この this ポインタは逆参照されてint[2][1]. これは、次の 2 つの添字に対して同じプロセスを経てint、適切な配列インデックスで最終的な左辺値になります。*(§5.3.1/1)の結果による左辺値です。

単項 * 演算子は間接参照を実行します。それが適用される式は、オブジェクト型へのポインター、または関数型へのポインターであり、結果は、式が指すオブジェクトまたは関数を参照する左辺値になります。

int左辺値も型であるため、この左辺値を介して実際のオブジェクトにアクセスすることはまったく問題ありintません (§3.10/10):

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

  • オブジェクトの動的タイプ
  • [...]

だから私が何かを見逃していない限り。このプログラムはよく定義されていると思います。

于 2013-03-07T23:58:35.313 に答える