21

私は最近、地元の図書館でわずか 2 ドルで販売されていた、優れたデータ構造の本、" Data Structures Using C " (c) 1991 を見つけました。本のタイトルが示すように、この本は C プログラミング言語を使用したデータ構造をカバーしています。

時代遅れであることはわかっていましたが、他の場所では見られないような高度な C トピックが多数含まれていることを知って、この本を入手しました。

案の定、5 分以内に C について知らなかったことがわかりました。unionキーワードについて説明しているセクションに出くわし、それを使用したことがなく、使用するコードを見たことがないことに気付きました。面白いことを学べたことに感謝し、すぐにその本を買いました。

労働組合とは何かをよく知らない人のために、この本は良い比喩を使って説明しています。

ユニオンの概念を完全に理解するには、その実装を検討する必要があります。構造は、メモリ領域へのロード マップと見なすことができます。メモリの解釈方法を定義します。ユニオンは、メモリの同じ領域に対していくつかの異なるロード マップを提供します。どのロード マップが現在使用されているかを判断するのは、プログラマの責任です。実際には、コンパイラは共用体の最大メンバーを格納するのに十分なストレージを割り当てます。ただし、そのストレージをどのように解釈するかを決定するのはロード マップです。

ユニオンを使用する不自然な状況やハックを簡単に思いつくことができました。(しかし、不自然な状況やハックには興味がありません...)

Union を使用すると、Union を使用しない場合よりも**よりエレガントに**問題を解決する実装を使用したり、見たりしたことがありますか?

ユニオンを使用した方がユニオンを使用しないよりも優れている/簡単である理由の簡単な説明を含めると、ボーナスが追加されます。

4

12 に答える 12

26

UNION は、非 OOP の世界である種のポリモーフィズムを実装します。通常、共通の部分があり、その部分に応じて残りの UNION を使用します。したがって、OOP 言語を持っておらず、過度のポインター演算を避けたい場合は、共用体の方が洗練されている場合があります。

于 2009-05-13T13:51:08.010 に答える
19

シフト/マスク操作の代わりに、たとえばレジスタにビットを設定するのに役立ちます。

typedef union {
    unsigned int as_int; // Assume this is 32-bits
    struct {
        unsigned int unused1 : 4;
        unsigned int foo : 4;
        unsigned int bar : 6;
        unsigned int unused2 : 2;
        unsigned int baz : 3;
        unsigned int unused3 : 1;
        unsigned int quux : 12;
    } field;
} some_reg;

注: パッキングがどの方法で行われるかは、マシンに依存します。

some_reg reg;
reg.field.foo = 0xA;
reg.field.baz = 0x5;
write_some_register(some_address, reg.as_int);

私はそこのどこかでいくつかの構文を吹き飛ばしたかもしれません.私のCは錆びています:)

編集:

ちなみに、これは逆の方法でも機能します。

reg.as_int = read_some_register(some_address);
if(reg.field.bar == BAR_ERROR1) { ...
于 2009-05-13T14:04:01.033 に答える
10

確かに、デバイス ドライバー (structいくつかの類似しているが異なる形式を持つことができるデバイスに送信するドライバー) のようなものを作成し、正確なメモリ配置が必要な場合に最適なツールです...

于 2009-05-13T13:50:01.647 に答える
8

C++ では、ユニオンに配置できるのは POD (プレーン オールド データ) 型のみであるため、これらはそれほど優れたソリューションではないことに注意してください。クラスにコンストラクタ、デストラクタがあり、コンストラクタやデストラクタ (およびその他の約 100 万の落とし穴) を持つクラスが含まれている場合、そのクラスは共用体のメンバーになることはできません。

于 2009-05-13T14:00:17.980 に答える
6

Union は、C/C++ でVARIANT のようなデータ型を実装する最も簡単な方法だと思います。

于 2009-05-13T13:51:50.943 に答える
5

これは、データ構造でスペースを浪費したくない場合に、データ転送プロトコルの仕様でよく使用されます。複数の相互に排他的なオプションに同じスペースを使用することで、メモリ スペースを節約できます。

例えば:

enum PacketType {Connect, Disconnect};
struct ConnectPacket {};
struct DisconnectPacket {};
struct Packet
{
    // ...
    // various common data
    // ...
    enum PacketType type;
    union
    {
        ConnectPacket connect;
        DisconnectPacket disconnect;
    } payload;
};

ConnectPacket 構造と DisconnectPacket 構造は同じスペースを占有しますが、パケットが同時に両方のタイプになることはできないため、問題ありません。列挙値は、共用体のどの部分が使用されているかを判別するために使用されます。ユニオンを使用することで、Packet 構造の共通部分の重複を避けることができました。

于 2009-05-13T14:10:07.137 に答える
4

大きな変数内の個々のバイトにアクセスする場合を考えてみましょう:

UInt32 x;
x = 0x12345678;
int byte_3 = x & 0x000000FF;          // 0x78
int byte_2 = (x & 0x0000FF00) >> 8;   // 0x56
int byte_1 = (x & 0x00FF0000) >> 16;  // 0x34
int byte_0 = (x & 0xFF000000) >> 24;  // 0x12

これは、ユニオンを使用するとはるかにエレガントになります。

typedef union
{
    UInt32 value;  // 32 bits
    Byte byte[4];  // 4 * 8 bits
}
UInt32_Bytes;

UInt32_Bytes x;
x.value = 0x12345678;
int byte_3 = x.byte[3];  // 0x78
int byte_2 = x.byte[2];  // 0x56
int byte_1 = x.byte[1];  // 0x34
int byte_0 = x.byte[0];  // 0x12

ユニオンを使用すると、個々のバイトにアクセスするためにビット マスクやシフト演算子を使用する必要がなくなります。また、バイト アクセスを明示的にします。

于 2009-05-13T13:59:35.003 に答える
4

これは float の IEEE ビット値を取得する非常に良い方法です (もちろん、float がシステム上で IEEE であると仮定します)。float* から int* へのキャストを伴うものはすべて、厳密なエイリアシング ルールに違反する危険があります。これは単なる理論上の話ではありません。高レベルの最適化は実際にコードを壊します。

技術的には、労働組合は問題に対処しません。実際には、既知のコンパイラはすべて、(a) 共用体の 1 つのメンバーを書き込んで別のメンバーを読み戻すことができ、(b) 書き込みを実行した後に読み取りを実行できます。少なくとも GCC は、共用体をレジスターにロールインして、すべてをノーオペレーションにすることができます (フロートが最初からレジスターに格納されていると仮定します)。

于 2009-05-13T14:23:50.547 に答える
2

ネットワークパケットの解析には、多くのコードでユニオンを使用しました。

ユニオンは最大の要素のサイズを割り当てます。最大メッセージサイズのバッファ要素を使用してユニオンを作成すると、パケット内の値に簡単にアクセスできます。

データ「c123456」がオンラインで到着し、値を解析してアクセスする必要があると想像してください。

  #include <iostream>
  using namespace std;

  struct msg
  {
     char header;
     union
     {
       char a[3];
       char b[2];
       char c[5];
       char d[6];
       char buf[10];
     } data;
  } msg;

  int main()
  {
    struct msg m;
    memcpy(&m, "c123456", sizeof("c123456"));

    cout << "m.header: " << m.header << endl;
    cout << "m.data.d: " << string(m.data.d,sizeof(m.data.d)) << endl;
    cout << "m.data.b: " << string(m.data.b,sizeof(m.data.b)) << endl;

    switch (m.header)
    {
     case 'a': cout << "a: " << string(m.data.a, sizeof(m.data.a)) << endl; break;
     case 'b': cout << "b: " << string(m.data.b, sizeof(m.data.b)) << endl; break;
     case 'c': cout << "c: " << string(m.data.c, sizeof(m.data.c)) << endl; break;
     default: break;
    }
  }

出力は次のようになります。

m.header: c
m.data.d: 123456
m.data.b: 12
c: 12345
于 2009-05-13T15:16:21.337 に答える
2

これが繰り返されていることは承知していますが、コード サンプルを投稿して、ネットワーク トラフィックを読み取るときにユニオンがどのようにエレガンスと効率を向上させるかを確認します。

#pragma packed(1)
struct header_t {
   uint16_t msg_id;
   uint16_t size;
};
struct command_t {
   uint8_t cmd;
};
struct position_t {
   uint32_t x;
   uint32_t y;
   uint32_t z;
};
// ... Rest of the messages in an IDS
struct message {
   header_t header;
   union {
      command_t command;
      position_t position;
   } body;
};
#pragma packed(0)
message read( int socket ) {
   message data;
   unsigned int readed = read( socket, &data, sizeof(header_t) );
   // error checks... readed bytes smaller than header size and such
   readed = read( socket, &(data.body), data.header.size ); 
   // error checks...
}

上記のスニペットでは、メッセージをその場で読み取ることができ、受信したオブジェクトの具体的なタイプを気にする必要はありません。ユニオンを使用しなかった場合は、ヘッダーを読み取り、サイズとタイプの両方を抽出し、適切なタイプのオブジェクトをインスタンス化する必要があります (階層内またはバリアント型内に boost::any/ として含めるため)。 boost::variant) を作成し、新しく作成されたスペースで 2 番目の読み取りを実行します。

このソリューションは、シミュレーターを制御するために広く使用されています (一部の企業は、DDS や HLA などの「新しい」テクノロジーを評価せず、シミュレーター用の未加工の UDP/TCP データに依存しています)。ネットワーク層では、アプリケーション層に供給する前に、内部データ構造 (ネットワークからホストへの変換、データのスケーリングなど) に変換される共用体を使用します。前に述べたように、パディングには常に注意する必要があります。

于 2009-09-02T19:11:10.287 に答える
1

markh44の答えと同様の方法で、大まかな種類のデータポリモーフィズムに一度使用しました。潜在的に使用したいいくつかの異なる種類のデータがありました。これらすべての型の共用体と、共用体と使用する型を定義するコードを含む構造体を作成しました。


union
{
    data_type_1;
    data_type_2;
    data_type_3;
} data_union;

typedef struct _TAG_DATA_WRAPPED_
{
    data_union data;
    int data_type; //better an enum
} WRAPPED_DATA;

WRAPPED_DATA loads_of_data[1024];


これが有利な理由についての質問に答えるには:

これにより、さまざまな種類のデータのリストまたは配列を簡単に割り当て、それらの型をプログラムで管理できます。もちろん、大きな問題はストレージ スペースです。タイプのストレージ サイズが非常に異なる場合、多くのスペースが無駄になる可能性があるためです。

于 2009-05-13T16:37:16.943 に答える