CとC++における共用体の目的 質問する

CとC++における共用体の目的 質問する

私は以前、組合を快適に使用していましたが、今日読んだときに驚きましたこの郵便受けそしてこのコードが

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

実際には未定義の動作です。つまり、最近書き込まれたもの以外の共用体のメンバーから読み取ると、未定義の動作が発生します。これが共用体の本来の使い方ではないとしたら、何が本来の使い方なのでしょうか? 誰か詳しく説明してくれませんか?

アップデート:

後から振り返って、いくつかの点を明確にしておきたいと思います。

  • この質問に対する答えは C と C++ で同じではありません。無知だった若い頃の私は、これを C と C++ の両方としてタグ付けしました。
  • C++11 の標準を徹底的に調べた後、非アクティブな共用体メンバーへのアクセス/検査が未定義/未指定/実装定義であると明確に述べることはできませんでした。私が見つけたのは §9.5/1 だけでした。

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

  • Cでは、(C99 TC3 - DR 283以降は合法です(パスカル・クオックに感謝(この問題を指摘していただきありがとうございます) ただし、読み取られた値が読み取られた型に対して無効 (いわゆる「トラップ表現」) である場合、これを実行しようとすると、未定義の動作が発生する可能性があります。それ以外の場合、読み取られた値は実装定義です。
  • C89/90 では、これを未指定の動作 (付録 J) として規定しており、K&R の本では実装定義であるとされています。K&R からの引用:

    これがユニオンの目的です。つまり、複数の型のいずれかを正当に保持できる単一の変数です。[...] 使用法が一貫している限り、取得される型は最後に保存された型である必要があります。現在ユニオンに保存されている型を追跡するのはプログラマの責任です。ある型として保存され、別の型として抽出された場合、結果は実装に依存します。

  • Stroustrup の TC++PL からの抜粋 (強調は筆者による)

    ユニオンの使用は、データの互換性にとって不可欠な場合があります [...] 「型変換」のために誤用されることもあります。

何よりも、この質問(タイトルは私が質問してから変更されていません)は、ユニオンの目的を理解する意図で提起されたものであり、標準が許可しているものではありません。たとえば、継承を使用してコードを再利用することは、もちろんC++標準で許可されていますが、C++言語の機能として継承を導入する目的や当初の意図ではなかったこれが、Andrey の回答が引き続き受け入れられている理由です。

ベストアンサー1

労働組合の目的はかなり明白ですが、何らかの理由で人々はそれを見逃してしまうことがよくあります。

ユニオンの目的は、異なる時間に異なるオブジェクトを格納するために同じメモリ領域を使用することでメモリを節約することです。それだけです。

それはホテルの一室のようなものです。そこには、異なる人々が、重なり合うことのない期間だけ滞在します。これらの人々は決して会うことはなく、通常はお互いについて何も知りません。部屋の時間配分を適切に管理することにより (つまり、異なる人々が同時に 1 つの部屋に割り当てられないようにすることにより)、比較的小規模なホテルでも、比較的多数の人々に宿泊施設を提供できます。それがホテルの目的です。

まさにこれがユニオンの機能です。プログラム内の複数のオブジェクトが重複しない値の有効期間を持つ値を保持していることがわかっている場合は、これらのオブジェクトをユニオンに「マージ」してメモリを節約できます。ホテルの部屋には、各瞬間に最大で 1 人の「アクティブ」なテナントがいるのと同じように、ユニオンには、プログラム時間の各瞬間に最大で 1 人の「アクティブ」なメンバーがあります。読み取り可能なのは「アクティブ」なメンバーだけです。他のメンバーに書き込むと、「アクティブ」ステータスが他のメンバーに切り替わります。

何らかの理由で、このユニオンの本来の目的が、まったく異なる方法で「上書き」されました。つまり、ユニオンの 1 つのメンバーを記述し、それを別のメンバーを通じて検査するのです。この種のメモリの再解釈 (別名「型パンニング」) は、 ユニオンの有効な使用法ではありません。これは通常、未定義の動作につながり、 C89/90 では実装定義の動作を生成すると説明されています。

編集:共用体を型パンニング(つまり、1つのメンバーを書き込んでから別のメンバーを読み込む)の目的で使用することについては、C99標準の技術的訂正の1つでより詳細な定義が与えられました(DR#257そしてDR#283ただし、これは、トラップ表現を読み取ろうとすることで未定義の動作が発生するのを防ぐものではないことに注意してください。

おすすめ記事