4

この回答のコメントでは、次のようなユニオンを使用して整数をバイトに分割することは未定義の動作になると言われています。その場所で提供されているコードは、これと同じではありませんが、類似しています。コードの未定義の動作関連の側面を変更した場合は、メモしてください。

union addr {
 uint8_t addr8[4];
 uint32_t addr32;
};

addr = {127, 0, 0, 1};これまでは、これは次のようなことを行い、対応する ものを取得するための優れたアプローチであると考えていuint32_tました。(システムのエンディアンによって異なる結果が生じる可能性があることは認めますが、問題は残ります。)

これは未定義の動作ですか?もしそうなら、なぜですか?( C++ の UB とは、非アクティブな共用体メンバーにアクセスすることを意味するかどうかはわかりません。 )


C99

  • この点では、C99 は明らかに C++03 にかなり近いです。

C++03

  • 共用体では、いつでもアクティブにできるデータ メンバーは最大 1 つです。つまり、データ メンバーの最大 1 つの値をいつでも共用体に格納できます。C++03、セクション 9.5 (1)、162 ページ

でも

  • POD 共用体に共通の初期シーケンスを共有する複数の POD 構造体が含まれる場合 [...]、POD 構造体メンバーのいずれかの共通の初期シーケンスを検査することが許可されます。
  • 2 つの POD 構造体 [...] 型は、同じ数の非静的データ メンバーを持ち、対応する非静的データ メンバーが (順番に) レイアウト互換型を持つ場合、レイアウト互換性がありますC++03、セクション 9.2 (14)、 157ページ
  • T1 と T2 の 2 つの型が同じ型である場合、T1 と T2 はレイアウト互換型です。C++03、セクション 3.9 (11)、53 ページ

結論

  • asuint8_t[4]uint32_tは同じタイプではありません (厳密なエイリアシングだと思います) (さらに、どちらも POD 構造体/共用体ではありません) 上記は実際に UB ですか?

C++11

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

4 に答える 4

10

C++ の UB とは、非アクティブな共用体メンバーにアクセスすることです。

基本的に、未定義の動作を呼び出さずに共用体から読み取ることができる唯一のメンバーは、最後に書き込まれたメンバーであることを意味します。つまり、 に書き込めばaddr32、 からしか読み取れaddr32addr8、その逆も可能です。

例もここで入手できます。

編集: これが UB であるかどうかについて多くの議論があったため、次の (完全に有効な) C++11 の例を検討してください。

union olle {
    std::string str;
    std::wstring wstr;
};

ここでは、str を有効にして wstr を読み取ることが問題になる可能性があることが明確にわかります。これは、placement new を実行してメンバーをアクティブ化する必要があるため、極端な例と見なすことができますが、仕様では実際にこのケースをカバーしており、アクティブなメンバーに関して他の方法で特別なケースと見なされることについては言及されていません。

于 2012-04-22T20:46:28.447 に答える
7

[編集: これが未定義の動作であるかどうかわからないので、以下の編集したセクションを読んでください。ただし、さらに確認できるまで、回答の大部分は同じままにします] はい、これは未定義の動作です。C++ 標準のセクション 9.5.1 には、次のように記載されています。

ユニオンでは、非静的データ メンバーの最大 1 つをいつでもアクティブにできます。つまり、非静的データ メンバーの最大 1 つの値をいつでもユニオンに格納できます。[ 注: 共用体の使用を簡素化するために、1 つの特別な保証が行われます: 標準レイアウト共用体に、共通の初期シーケンス (9.2) を共有する複数の標準レイアウト構造体が含まれている場合、およびこの標準レイアウト共用体タイプのオブジェクトが標準レイアウトの構造体の 1 つを含む場合、標準レイアウトの構造体メンバーの共通の初期シーケンスを検査することが許可されています。9.2 を参照してください。— 終了注記]

これは、最後に書き込まれたメンバーのみが有効に読み取られることを意味します (他のメンバーからの読み取りは、技術的に未定義の動作です)。いつでもアクティブにできる組合メンバーは1 人だけです。2つではありません。

なぜ?と思うかもしれません。あなたの例を考えてみましょう。C++ は、 のエンディアンを義務付けていませんaddr32。ビッグ エンディアン、リトルエンディアン、ミドル エンディアンのいずれかです。に書き込みaddr8、次に から読み取るaddr32場合、この場合のエンディアンが原因で、C++ は正しい値を取得できることを保証できません。あるコンピューターでは、ある値である可能性があり、別のコンピューターでは異なる値である可能性があります。したがって、そうする (つまり、あるメンバーに書き込み、別のメンバーを読み取る) ことは、未定義の動作です。

編集:「アクティブ」が何を意味するのか疑問に思っている人のために、ユニオンに関する MSDN ドキュメントには次のように記載されています。

共用体のアクティブなメンバーは、値が最後に設定されたメンバーであり、そのメンバーのみが有効な値を持ちます。

編集編集:これを行う動作は未定義であると常に考えていましたが、R. Martinho Fernandes のコメントと回答の後、MSDN からの引用を再読した後、今ではよくわかりません。値は確かに未指定/未定義ですが、動作が (未定義の値は異なる結果が返される可能性があることを意味し、未定義の動作はシステムがクラッシュする可能性があることを意味し、2 つは異なるものです) かどうかはわかりません)。私はこれをさらに検討し、より明確な答えを見つけることができるかどうかを知るために、私が知っている他の人と話し合うつもりです.

ただし、一般に、ユニオン内の非アクティブなメンバーを読み取ることは未定義の動作になる可能性があると言っても安全だと思いますが(もちろん、標準の特別な注意を除く)、常にそうであるかどうかはわかりません(つまり、私が引用した C++ 標準のセクションの特記事項を超えたいくつかの例外があるかもしれません)。

于 2012-04-22T20:52:12.643 に答える
5

率直に言って、これを行うことは未定義の振る舞いであるという標準の言及を見つけることができません。この規格は、組合の「アクティブメンバー」の概念を定義していますが、アクティブメンバーの変更方法(§9.5p4)と定数式の定義(§5.9p2)以外の目的でそのアイデアを使用しているようには見えません。 )。具体的には、アクティブなメンバーまたは非アクティブなメンバーにアクセスすることの有効性について明確に言及していないようです。

私が見る限り、次のようなものは、未定義の動作である厳密なエイリアシング違反を引き起こす可能性があります。

union example0 {
    short some_other_view[sizeof(double)/sizeof(short)];
    double value;
};

ユニオンには特別なルールがあるため、これによって厳密なエイリアシング違反が発生することはありません。エイリアシングできないタイプを使用して同じメモリ位置にアクセスした場合、つまり「通常の」厳密なエイリアシング違反が発生した場合に発生します。

ただし、エイリアシングルールに関しては例外があるためchar、次の場合に同じ種類の違反が発生することはありません。

union example1 {
    char byte_view[sizeof(double)];
    double value;
};

私が見る限り、次のコードに未定義の動作を残すものは標準にはありません。

example1 e;
e.value = 10.0;
std::out << e.byte_view[0];
于 2012-04-22T21:21:46.830 に答える
5

基本的に、C++ では共用体のアクティブなメンバーのみにアクセスできるためです。

これは、設定した場合、設定addr8するまでその 1 つだけにアクセスする必要があることを意味しaddr32ます。これにより、アクセスできるようになります。あるメンバーから別のメンバーのデータにアクセスするように設定すると、未定義の動作が発生するはずです。

メンバーは設定時にアクティブと見なされ、別のメンバーがアクティブになるまでそのままになります。

于 2012-04-22T20:46:46.987 に答える