139

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

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

4

5 に答える 5

150

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

6.5.2.3 構造体と共用体のメンバー

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

C++ の状況:

9.5 ユニオン [class.union]

共用体では、非静的データ メンバーの最大 1 つがいつでもアクティブになることができます。つまり、非静的データ メンバーの最大 1 つの値をいつでも共用体に格納できます。

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

C++ でユニオン型パニング許可されているかどうかを判断するには、さらに検索する必要があります。は 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 の適切なアラインメントとサイズを持つストレージが取得されたとき — オブジェクトに重要な初期化がある場合、その初期化が完了したとき。

したがって、共用体に含まれるプリミティブ型 (当然のことながら単純な初期化がある) の場合、オブジェクトの有効期間には、少なくとも共用体自体の有効期間が含まれます。これにより、呼び出すことができます

3.9.2 複合型 [basic.compound]

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

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

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

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

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

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

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

つまり、非アクティブな共用体メンバーに左辺値を合法的に形成することはできますが (これが、構築せずに非アクティブなメンバーに割り当てても問題ない理由です)、それは初期化されていないと見なされます。

于 2012-08-16T23:41:10.043 に答える
30

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

9.5 ユニオン

共用体では、非静的データ メンバーの最大 1 つを常にアクティブにすることができます。つまり、非静的データ メンバーの最大 1 つの値を任意の時点で共用体に格納できます。

格納されている値が 1 つだけの場合、別の値を読み取るにはどうすればよいでしょうか? そこにないだけです。


gcc のドキュメントでは、実装定義の動作の下にこれがリストされています

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

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

これは、C 標準では要求されていないことを示しています。


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

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

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

于 2012-07-07T07:48:43.203 に答える
19

標準が未定義の動作であると言うのに最も近いのは、共通の初期シーケンスを含む共用体の動作を定義する場所だと思います (C99、§6.5.2.3/5):

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

C++11 では、§9.2/19 で同様の要件/許可が与えられています。

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

どちらも直接は述べていませんが、これらはいずれも、メンバーの「検査」(読み取り) が「許可される」の、1) メンバー (の一部) が最後に書き込まれた場合、または 2) 共通のイニシャルの一部である場合に限られることを強く示唆しています。順序。

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

于 2012-08-10T18:06:46.540 に答える
12

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

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

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

于 2012-08-16T22:00:52.497 に答える
-3

これを例でよく説明します。
次の共用体があるとします。

union A{
   int x;
   short y[2];
};

sizeof(int)私はそれが4を与え、それsizeof(short)が2 を与えると仮定します.それをうまく
書くunion A a = {10}と、タイプAの新しい変数を作成し、値10を入れます.

メモリは次のようになります: (すべてのユニオン メンバーが同じ場所を取得することに注意してください)

       | | × |
       | | y[0] | y[1] |
       ----------------------------------------------
   a-> |0000 0000|0000 0000|0000 0000|0000 1010|
       ----------------------------------------------

ご覧のとおり、ax の値は 10、ay 1の値は 10、ay[0] の値は 0 です。

さて、これをしたらどうなるでしょうか?

a.y[0] = 37;

メモリは次のようになります。

       | | × |
       | | y[0] | y[1] |
       ----------------------------------------------
   a-> |0000 0000|0010 0101|0000 0000|0000 1010|
       ----------------------------------------------

これにより、ax の値が 2424842 (10 進数) になります。

ここで、union に float または double がある場合、正確な数値を格納する方法のために、メモリ マップはさらに混乱します。ここで入手できる詳細情報。

于 2012-08-17T07:00:47.880 に答える