なぜCユニオンが必要なのですか?


236

ユニオンはいつ使用すべきですか?なぜ必要なのですか?

回答:


252

共用体は、整数と浮動小数点のバイナリ表現間の変換によく使用されます。

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

これは、C標準によると技術的には未定義の動作ですが(最後に書き込まれたフィールドを読み取ることのみが想定されています)、実質的にどのコンパイラでも明確に定義された方法で動作します。

ユニオンは、Cに疑似ポリモーフィズムを実装するために使用されることもあります。これには、構造体に含まれるオブジェクトのタイプを示すタグを付け、可能なタイプを結合します。

enum Type { INTS, FLOATS, DOUBLE };
struct S
{
  Type s_type;
  union
  {
    int s_ints[2];
    float s_floats[2];
    double s_double;
  };
};

void do_something(struct S *s)
{
  switch(s->s_type)
  {
    case INTS:  // do something with s->s_ints
      break;

    case FLOATS:  // do something with s->s_floats
      break;

    case DOUBLE:  // do something with s->s_double
      break;
  }
}

これにより、サイズstruct Sが28バイトではなく12バイトになりました。


ufの代わりにuyがあるはずです
Amit Singh Tomar

1
floatをintegerに変換することを想定した例は機能しますか?intとfloatは異なる形式でメモリに格納されているので、私はそうは思いません。あなたの例を説明できますか?
spin_eight

3
@spin_eight:floatからintへの「変換」ではありません。「floatのバイナリ表現をintであるかのように再解釈する」のようなものです。出力は3ではありません:ideone.com/MKjwon Adamが16進数として印刷している理由がわかりません。
内部石

@Adam Rosenfield出力に整数が表示されないため、変換を実際に理解していませんでした:p
The Beast

2
未定義の動作であることに関する免責事項を削除する必要があると思います。実際には、定義された動作です。C99標準の脚注82を参照してください。ユニオンオブジェクトのコンテンツにアクセスするために使用されるメンバーが、オブジェクトに値を格納するために最後に使用されたメンバーと同じでない場合、値のオブジェクト表現の適切な部分は、次のように再解釈されます。 6.2.6で説明されている新しいタイプのオブジェクト表現(「タイプパニング」と呼ばれることもあるプロセス)。これはトラップの表現かもしれません。
クリスチャンギボンズ

135

ユニオンは、組み込みプログラミングや、ハードウェア/メモリへの直接アクセスが必要な状況で特に役立ちます。これは簡単な例です:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

その後、次のようにしてregにアクセスできます。

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

エンディアン(バイト順)とプロセッサアーキテクチャはもちろん重要です。

もう1つの便利な機能はビット修飾子です。

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

このコードを使用すると、レジスタ/メモリアドレスの単一ビットに直接アクセスできます。

x = reg.bits.b2;

3
ここでの回答と上記の@Adam Rosenfieldの回答を組み合わせると、完璧な補完ペアになります。union 内でstructを使用することを示し、彼はstruct内でunionを使用することを示します。それは私が同時に両方を必要とすることがわかりました:組み込みシステムのスレッド間でCにいくつかのファンシーなメッセージパッシング多態性を実装するための構造体内の共用体内の構造体、そしてあなたの両方の答えを一緒に見なければ私は気づかなかったでしょう。
ガブリエルステープルズ

1
私は間違っていました。それは、最も内側のネストから最も外側のレベルまで、私が書いたとおりに左にネストされた、構造体内の共用体内の構造体内の共用体です。さまざまなデータ型の値を許可するために、最も内側のレベルに別の共用体を追加する必要がありました。
ガブリエルステープルズ

64

低レベルのシステムプログラミングが妥当な例です。

IIRC、ハードウェアレジスタをコンポーネントビットに分解するためにユニオンを使用しました。したがって、コンポーネントビットに8ビットレジスタにアクセスできます(実際には、これを行った日ですが;-)。

(正確な構文を忘れていますが...)この構造により、制御レジスタにcontrol_byteとして、または個々のビットを介してアクセスできます。ビットが特定のエンディアンの正しいレジスタビットにマッピングされるようにすることが重要です。

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;

3
これは素晴らしい例です!この手法を組み込みソフトウェアで使用する方法の例を次に示します。edn.com
design

34

オブジェクト指向の継承に代わるものとして、いくつかのライブラリで見たことがあります。

例えば

        Connection
     /       |       \
  Network   USB     VirtualConnection

接続の「クラス」を上記のいずれかにしたい場合は、次のように記述できます。

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

libinfinityでの使用例:http ://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74


33

ユニオンを使用すると、相互に排他的なデータメンバーが同じメモリを共有できます。これは、組み込みシステムなど、メモリが不足している場合に非常に重要です。

次の例では:

union {
   int a;
   int b;
   int c;
} myUnion;

この共用体は、3つの別個のint値ではなく、1つのintのスペースを占有します。ユーザが値を設定した場合、その後の値セットBは、それがの値上書きするそれらは両方とも同じメモリロケーションを共有しているからです。


29

たくさんの使い方。やるgrep union /usr/include/*か、または同様のディレクトリで。ほとんどの場合、unionはにラップされstruct、構造体の1つのメンバーは、共用体のどの要素にアクセスするかを指示します。たとえばman elf、実際の実装のチェックアウト。

これが基本原則です。

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}

まさに私が探していたもの!省略記号パラメータを置き換えるのに非常に便利です:)
Nicolas Voron

17

これは、私自身のコードベースからの結合の例です(メモリから、言い換えると正確ではない可能性があります)。私が作成したインタプリタに言語要素を格納するために使用されました。たとえば、次のコード:

set a to b times 7.

次の言語要素で構成されています。

  • シンボル[セット]
  • 変数[a]
  • 記号[へ]
  • 変数[b]
  • シンボル[回]
  • 定数[7]
  • シンボル[。]

言語要素は、#define '値:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

そして、次の構造が各要素を格納するために使用されました

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

次に、各要素のサイズは最大の共用体のサイズでした(型には4バイト、共用体には4バイトですが、これらは典型的な値ですが、 実際のサイズは実装によって異なります)。

「set」要素を作成するには、次を使用します。

tElem e;
e.typ = ELEM_SYM_SET;

「variable [b]」要素を作成するには、次を使用します。

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

「constant [7]」要素を作成するには、次を使用します。

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

フロートを含めるように簡単に拡張できます(float flt)や有理数(struct ratnl {int num; int denom;})やその他のタイプます。

基本的な前提は、strvalはメモリ内で隣接しておらず、実際にはオーバーラップしているため、構造がメモリの場所に基づいて0x1010おり、整数とポインタの両方がここに示されている同じメモリブロックで異なるビューを取得する方法です。4バイト:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

構造内にある場合は、次のようになります。

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+

make sure you free this laterコメントを定数要素から削除する必要がありますか?
Trevor

はい、@ Trevor、あなたが過去4年間でそれを最初に見たとは信じられませんが:-)修正されました。ありがとうございます。
paxdiablo 2013年

7

さまざまな方法で使用される可能性のあるメモリの再利用が容易になると思います。つまり、メモリの節約です。たとえば、短い文字列と数値を保存できる「バリアント」構造体を実行したいとします。

struct variant {
    int type;
    double number;
    char *string;
};

32ビットシステムでは、これにより、のインスタンスごとに少なくとも96ビットまたは12バイトが使用されますvariant

共用体を使用すると、サイズを64ビットまたは8バイトに減らすことができます。

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

さらに異なる変数型などを追加したい場合は、さらに保存することができます。voidポインターをキャストして同様のことを行うことができるのは本当かもしれません-しかし、ユニオンは、型だけでなく、よりアクセスしやすくします安全。このような節約は大きな音ではありませんが、この構造体のすべてのインスタンスに使用されるメモリの3分の1を節約しています。


5

このタイプの柔軟な構造が必要になる特定の機会を考えるのは難しいです。おそらく、異なるサイズのメッセージを送信するメッセージプロトコルの場合ですが、それでも、おそらくより優れた、よりプログラマーフレンドリーな代替手段があります。

ユニオンは他の言語のバリアント型に少し似ています-ユニオンは一度に1つしか保持できませんが、宣言する方法によっては、intやfloatなどになる場合があります。

例えば:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnionには、最後に設定した内容応じて、intまたはfloatのみが含まます。これを行う:

MYUNION u;
u.MyInt = 10;

uは10に等しいintを保持します。

u.MyFloat = 1.0;

uは1.0に等しい浮動小数点数を保持します。それはもはやintを保持していません。明らかに、printf( "MyInt =%d"、u.MyInt);を試してみると、その後、エラーが発生する可能性がありますが、具体的な動作はわかりません。

共用体のサイズは、その最大フィールド(この場合はfloat)のサイズによって決まります。


1
sizeof(int) == sizeof(float)== 32)通常。
Nick T

1
記録のために、int型の印刷後、floatに割り当てることになるではない、コンパイラやランタイム環境でもないとして、エラーの原因とノウハウ値が有効です。もちろん、出力されるintはほとんどの目的にとって無意味です。これは、floatのメモリ表現であり、intとして解釈されます。
ジェリーB

4

ユニオンは、ハードウェア、デバイス、またはネットワークプロトコルによって定義された構造体をモデル化する場合、または多数のオブジェクトを作成してスペースを節約する場合に使用されます。ただし、実際には95%の時間は必要ありません。デバッグしやすいコードを使用してください。


4

これらの回答の多くは、あるタイプから別のタイプへのキャストに対応しています。私は同じタイプのユニオンをより多く使用します(つまり、シリアルデータストリームを解析する場合)。これにより、フレーム化されたパケットの解析/構築が簡単になります。

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

編集 エンディアンと構造体の埋め込みに関するコメントは有効であり、大きな懸念事項です。私はこのコード本体をほぼ完全に組み込みソフトウェアで使用しており、そのほとんどはパイプの両端を制御できました。


1
次の理由により、2つの異なるプラットフォーム間でデータが交換されている場合、このコードは機能しません(ほとんどの場合)。1)エンディアンが異なる場合があります。2)構造のパディング。
Mahori 2014年

@Raviエンディアンとパディングに関する懸念に同意します。ただし、組み込みプロジェクトでのみこれを使用したことを知っておく必要があります。そのほとんどがパイプの両端を制御していました。
アダムルイス

1

組合は素晴らしい。私が見たユニオンの賢い使い方の1つは、イベントを定義するときにそれらを使用することです。たとえば、イベントが32ビットであると決定する場合があります。

さて、その32ビット内で、イベントの送信者の識別子として最初の8ビットを指定したいと思うかもしれません...場合によってはイベント全体を扱い、時にはそれを分析してそのコンポーネントを比較します。ユニオンは、両方を実行する柔軟性を提供します。

組合イベント
{
  unsigned long eventCode;
  unsigned char eventParts [4];
};

1

VARIANTCOMインターフェイスで使用されているのはどうですか?「type」と「type」フィールドに応じて処理される実際の値を保持する共用体の2つのフィールドがあります。


1

学校では、次のような組合を使用しました。

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

色をより簡単に処理するためにそれを使用しました。>>および<<演算子を使用する代わりに、char配列の異なるインデックスを調べる必要がありました。


1

組み込みデバイスをコーディングするときにunionを使用しました。私は16ビット長のC intを持っています。また、EEPROMの読み取り/保存が必要な場合は、上位8ビットと下位8ビットを取得する必要があります。だから私はこのように使いました:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

シフトする必要がないため、コードが読みやすくなります。

一方、stlアロケータにunionを使用する古いC ++ stlコードをいくつか見ました。興味があれば、sgi stlソースコードを読むことができます。以下がその一部です。

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

1
あなたは、グループ化する必要はないでしょうstruct、あなたの周りにhigher/をlower?現在、両方とも最初のバイトのみを指す必要があります。
マリオ

@マリオああ、私は手で書いて、それを忘れてしまいました。ありがとう
Mu Qiao

1
  • 異なるレコードタイプを含むファイル。
  • さまざまなリクエストタイプを含むネットワークインターフェース。

これを見てください:X.25バッファーコマンドの処理

多くの可能なX.25コマンドの1つがバッファに受信され、すべての可能な構造のUNIONを使用して適切に処理されます。


これらの例の両方について説明していただけますか。つまり、これらが組合とどのように関連しているかを意味します
Amit Singh Tomar '29

1

Cの初期のバージョンでは、すべての構造宣言が共通のフィールドセットを共有していました。与えられた:

struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};

コンパイラーは基本的に、構造体のサイズ(および場合によっては配置)のテーブルと、構造体のメンバーの名前、タイプ、およびオフセットの個別のテーブルを生成します。コンパイラは、(メンバーのようにメンバーがどの構造に属しているのを追跡していなかった、と二つの構造が同じ名前を持つメンバーを持つことができるようになる場合にのみ、タイプと一致オフセットqstruct xstruct y)。pが任意の構造体型へのポインタである場合、p-> qは "q"のオフセットをポインタpに追加し、結果のアドレスから "int"をフェッチします。

上記のセマンティクスを考えると、関数によって使用されるすべてのフィールドが問題の構造内の有用なフィールドと並んでいれば、複数の種類の構造に対していくつかの有用な操作を交換して実行できる関数を作成することができました。これは便利な機能であり、問​​題の構造体のタイプに対する構造体アクセスに使用されるメンバーを検証するようにCを変更すると、同じアドレスに複数の名前付きフィールドを含むことができる構造体を持たせる手段がなければ、それを失うことになります。「ユニオン」タイプをCに追加することで、そのギャップをいくぶん埋めることができました(IMHOはそうではありませんでしたが、そうであったはずです)。

そのギャップを埋めるためのユニオンの機能の本質的な部分は、ユニオンメンバーへのポインターがそのメンバーを含む任意のユニオンへのポインターに変換でき、任意のユニオンへのポインターが任意のメンバーへのポインターに変換できるという事実でした。C89標準では、aにT*直接U*キャストすることは、それをTandの両方を含む共用体型へのポインターにキャストすることと同等であると明確に述べていませんがU、それをにキャストするとU*、後者のキャストシーケンスの定義された動作は、共用体型が使用されており、標準はTto からの直接キャストに対して逆のセマンティクスを指定していませんU。さらに、関数が原因不明のポインターを受け取った場合、を介してオブジェクトを書き込む動作T*T*U*を介してオブジェクトを読み取ることはU*、型Tを介してunion viaメンバーを書き込み、型として読み取ることと同等ですU。これは、いくつかのケースで標準定義されます(たとえば、共通初期シーケンスメンバーにアクセスする場合)および実装定義(Undefinedではなく) )残りのため。プログラムがユニオンタイプの実際のオブジェクトでCIS保証を利用することはまれでしたが、起源が不明なオブジェクトへのポインターはユニオンメンバーへのポインターのように動作し、それに関連付けられた動作保証が必要であるという事実を利用することははるかに一般的でした。


この例を挙げていただけますか: `複数の種類の構造体に対していくつかの有用な操作を交換可能に実行できる関数を作成することが可能でした`。同じ名前の複数の構造体メンバーをどのように使用できますか?2つの構造が同じデータアライメントを持ち、したがって例のように同じ名前と同じオフセットを持つメンバーがある場合、どの構造から実際のデータを生成しますか?(値)。2つの構造体の配置とメンバーは同じですが、値が異なります。詳しく説明してもらえますか
Herdsman

@Herdsman:Cの初期のバージョンでは、構造体のメンバー名が型とオフセットをカプセル化していました。タイプとオフセットが一致した場合に限り、異なる構造の2つのメンバーが同じ名前を持つ可能性があります。構造体メンバーfoointオフセット8の場合、anyPointer->foo = 1234;「anyPointerでアドレスを取得し、8バイトずらして、値1234の整数ストアを結果のアドレスに実行します。コンパイラーは、anyPointer識別されたかどうかを知る必要も、気にする必要もありません。fooメンバーにリストされていた構造タイプ
スーパーキャット

ポインターを使用すると、ポインターの「起源」に関係なく任意のアドレスを逆参照できますが、それは事実ですが、データをフェッチできる場合、コンパイラーが構造体のメンバーとそれらの名前のテーブルを保持するコンパイラーのポイントは何ですか(投稿であなたが言ったように)特定の構造体のメンバーのアドレスを知っているだけのポインターで?そして、コンパイラがanyPointerstructメンバーで識別されるかどうかを知らない場合、コンパイラはto have a member with the same name only if the type and offset matchedあなたの投稿のこれらの状態をどのようにチェックしますか?
牧夫

@Herdsman:の正確な動作はp->fooのタイプとオフセットに依存するため、コンパイラーは構造体メンバーの名前のリストを保持しますfoo。本質的に、のp->foo省略形でした*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)。後者の質問については、コンパイラーがstructメンバー定義を検出した場合、その名前のメンバーが存在しないか、その名前のメンバーが同じタイプとオフセットを持つ必要があります。一致しないstructメンバー定義が存在する場合、は不正確だったと思いますが、エラーの処理方法がわかりません。
スーパーキャット

0

シンプルで非常に便利な例は...

想像してみてください:

あなたが持っているuint32_t array[2]とバイト・チェーンの3番目と4番目のバイトにアクセスしたいです。あなたがすることができます*((uint16_t*) &array[1])。しかし、これは悲しいことにエイリアシングの厳密な規則に違反しています。

ただし、既知のコンパイラでは、次のことが可能です。

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

技術的にはこれはまだルール違反です。しかし、すべての既知の標準はこの使用法をサポートしています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.