web-dev-qa-db-ja.com

非アクティブなユニオンメンバーと未定義の動作にアクセスしていますか?

最後のセット以外のunionメンバーへのアクセスはUBであるという印象を受けましたが、しっかりした参照が見つからないようです(UBであると主張するが標準からのサポートなしで回答する場合を除く) 。

だから、それは未定義の動作ですか?

108
Luchian Grigore

混乱は、Cが共用体を介した型のパニングを明示的に許可しているのに対し、C++( c ++ 11 )にはそのような許可がないことです。

c11

6.5.2.3構造と組合員

95)ユニオンオブジェクトの内容を読み取るために使用されるメンバーが、オブジェクトに値を格納するために最後に使用されたメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、新しいオブジェクト表現として再解釈されます6.2.6で説明されているように入力します(プロセスは '' type punning ''と呼ばれることもあります)。これはトラップ表現である可能性があります。

C++の状況:

c ++ 11

9.5ユニオン[class.union]

ユニオンでは、最大で1つの非静的データメンバーをいつでもアクティブにできます。つまり、最大で1つの非静的データメンバーの値をいつでもユニオンに格納できます。

C++には、共通の初期シーケンスを持つstructsを含む共用体の使用を許可する言語があります。ただし、これは型のパニングを許可しません。

C++でunion type-punning isが許可されているかどうかを判断するには、さらに検索する必要があります。 c99 はC++ 11の規範的なリファレンスであることを思い出してください(C99にはC11と同様の言語があり、ユニオン型のパニングが許可されています)。

3.9型[basic.types]

4-タイプTのオブジェクトのオブジェクト表現は、タイプTのオブジェクトによって取得されるN個のunsigned charオブジェクトのシーケンスです。ここで、Nはsizeof(T)に等しくなります。オブジェクトの値表現は、T型の値を保持するビットのセットです。簡単にコピー可能な型の場合、値表現は、実装の1つの個別要素である値を決定するオブジェクト表現のビットのセットです。定義された値のセット。 42
42)その意図は、C++のメモリモデルがISO/IEC 9899プログラミング言語Cのメモリモデルと互換性があることです。

読むと特に面白い

3.8オブジェクトの有効期間[basic.life]

タイプTのオブジェクトのライフタイムは、次の場合に開始されます。—タイプTの適切なアライメントとサイズのストレージが取得され、—オブジェクトに重要な初期化がある場合、その初期化は完了します。

そのため、ユニオンに含まれるプリミティブ型(ipso factoが簡単な初期化を持っている)の場合、オブジェクトのライフタイムには少なくともユニオン自体のライフタイムが含まれます。これにより、呼び出すことができます

3.9.2複合タイプ[basic.compound]

タイプTのオブジェクトがアドレスAにある場合、値が取得された方法に関係なく、値がアドレスAであるタイプcv T *のポインターはそのオブジェクトを指していると言われます。

関心のある操作が型パニング、つまり非アクティブなunionメンバーの値を取得し、そのメンバーによって参照されるオブジェクトへの有効な参照があることを前提にすると、その操作は左辺値になります-右辺値変換:

4.1左辺値から右辺値への変換[conv.lval]

非関数、非配列型Tのglvalueはprvalueに変換できます。 Tが不完全な型である場合、この変換を必要とするプログラムは不正な形式です。 glvalueが参照するオブジェクトがT型のオブジェクトではなく、Tから派生した型のオブジェクトでもない場合、またはオブジェクトが初期化されていないため、この変換を必要とするプログラムには未定義の動作があります。

問題は、非アクティブなユニオンメンバであるオブジェクトがストレージによってアクティブなユニオンメンバに初期化されるかどうかです。私が知る限り、これはそうではありません。

  • 共用体がchar配列ストレージにコピーされて戻される(3.9:2)、または
  • ユニオンがバイト単位で同じタイプの別のユニオンにコピーされます(3.9:3)、または
  • iSO/IEC 9899に準拠するプログラム要素(定義されている限り)(3.9:4注42)により、言語境界を越えてユニオンにアクセスします。

非アクティブなメンバーによる共用体へのアクセス定義済みであり、オブジェクトと値の表現に従うように定義されている場合、上記のいずれの介在もないアクセスは未定義の動作です。もちろん、実装は未定義の動作が発生しないと想定するため、このようなプログラムで実行できる最適化に影響を与えます。

つまり、非アクティブなユニオンメンバに対して正当に左辺値を形成できますが(これが、構成なしで非アクティブなメンバに割り当てるのは問題ない理由です)、初期化されていないと見なされます。

119
ecatmur

C++ 11標準はこのように言っています

9.5ユニオン

ユニオンでは、最大で1つの非静的データメンバーをいつでもアクティブにできます。つまり、最大で1つの非静的データメンバーの値をいつでもユニオンに格納できます。

1つの値のみが保存されている場合、どのようにして別の値を読み取ることができますか?それはそこにありません。


Gccのドキュメントには、これが Implementation defined behavior の下にリストされています。

  • ユニオンオブジェクトのメンバーは、異なるタイプのメンバーを使用してアクセスされます(C90 6.3.2.3)。

オブジェクトの表現の関連するバイトは、アクセスに使用されるタイプのオブジェクトとして扱われます。型のパニングを参照してください。これはトラップ表現である可能性があります。

これはC標準では必須ではないことを示しています。


2016-01-05:コメントを通じてリンクされました C99 Defect Report#28 これは、C標準ドキュメントに脚注として同様のテキストを追加します。

78a)ユニオンオブジェクトのコンテンツにアクセスするために使用されるメンバーが、オブジェクトに値を格納するために最後に使用されたメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、新しいオブジェクト表現として再解釈されます6.2.6で説明されているように入力します(プロセスは「タイプパンニング」と呼ばれることもあります)。これはトラップ表現である可能性があります。

しかし、脚注は標準の規範ではないことを考慮して、それが多くを明確にするかどうかはわかりません。

26
Bo Persson

標準が未定義の動作であると言うのに最も近いのは、共通の初期シーケンス(C99、§6.5.2.3/ 5)を含むユニオンの動作を定義するところです。

ユニオンの使用を簡素化するために、1つの特別な保証が行われます:ユニオンに共通の初期シーケンスを共有する複数の構造が含まれる場合(以下を参照)、ユニオンオブジェクトに現在これらの構造の1つが含まれている場合、共通の検査が許可されますユニオンの完全な型の宣言が見える任意の場所のそれらのいずれかの最初の部分。対応するメンバーが1つ以上の初期メンバーのシーケンスに対して互換性のある型(およびビットフィールドの場合は同じ幅)を持っている場合、2つの構造は共通の初期シーケンスを共有します。

C++ 11は、§9.2/ 19で同様の要件/許可を与えます。

標準レイアウト共用体に、共通の初期シーケンスを共有する2つ以上の標準レイアウト構造体が含まれている場合、および標準レイアウト共用体オブジェクトにこれらの標準レイアウト構造体のいずれかが現在含まれている場合、任意の共通の初期部分の検査が許可されますそのうちの。対応するメンバーにレイアウト互換型があり、どちらのメンバーもビットフィールドではないか、両方が1つ以上の初期メンバーのシーケンスに対して同じ幅のビットフィールドである場合、2つの標準レイアウト構造体は共通の初期シーケンスを共有します。

どちらも直接述べていませんが、これらは両方とも、メンバーを「検査」(読み取り)することを「許可」するという強い意味を持ちますのみ if 1)それは(最近の)メンバー、または2 )は、共通の初期シーケンスの一部です。

それはそうでなければ行うことは未定義の動作であるという直接の声明ではありませんが、私が知っている最も近いものです。

17
Jerry Coffin

利用可能な回答でまだ言及されていないものは、セクション6.2.5の段落21の脚注37です。

ユニオン型のオブジェクトには一度に1つのメンバーしか含めることができないため、集約型にはユニオン型が含まれないことに注意してください。

この要件は、メンバーを書いて別のメンバーを読んではいけないことを明確に暗示しているようです。この場合、仕様の欠如による未定義の動作である可能性があります。

11
mpu