ユニオンはいつ使用する必要がありますか?なぜ必要なのですか?
ユニオンは、整数と浮動小数点のバイナリ表現間の変換によく使用されます。
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バイトにすることができます。
ユニオンは、組み込みプログラミングまたはハードウェア/メモリへの直接アクセスが必要な状況で特に役立ちます。簡単な例を次に示します。
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;
エンディアンネス(バイト順)とプロセッサアーキテクチャはもちろん重要です。
別の便利な機能はビット修飾子です:
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;
低レベルのシステムプログラミングは妥当な例です。
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;
オブジェクト指向の継承の代わりとして、いくつかのライブラリで見ました。
例えば。
Connection
/ | \
Network USB VirtualConnection
Connectionの「クラス」を上記のいずれかにしたい場合は、次のように記述できます。
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
ユニオンでは、相互に排他的なデータメンバーが同じメモリを共有できます。これは、組み込みシステムなど、メモリが不足している場合に非常に重要です。
次の例では:
union {
int a;
int b;
int c;
} myUnion;
この結合は、3つの個別のint値ではなく、単一のintのスペースを占有します。ユーザーがaの値を設定してからbの値を設定すると、両方が共有されているためaの値が上書きされます同じメモリ位置。
多くの使用法。 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;
}
ここに、私自身のコードベースからの結合の例があります(正確であるとは限らないため、メモリと言い換えから)。私が作成したインタプリタに言語要素を保存するために使用されました。たとえば、次のコード:
set a to b times 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バイトです。これらは一般的な値ですが、実際のサイズは実装によって異なります)。
「セット」要素を作成するには、次を使用します。
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;}
)およびその他のタイプを含めるように簡単に拡張できます。
基本的な前提は、str
とval
はメモリ内で連続しておらず、実際にはオーバーラップしているため、ここに示すように、同じメモリブロックで異なるビューを取得する方法です。メモリ位置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 | |
+-------+
さまざまな方法で使用される可能性のあるメモリの再利用、つまりメモリの節約が容易になると思います。例えば。短い文字列と数値を保存できる「バリアント」構造体を作成する必要があります。
struct variant {
int type;
double number;
char *string;
};
32ビットシステムでは、variant
の各インスタンスに少なくとも96ビットまたは12バイトが使用されます。
ユニオンを使用すると、サイズを64ビットまたは8バイトに減らすことができます。
struct variant {
int type;
union {
double number;
char *string;
} value;
};
より多くの異なる変数タイプなどを追加したい場合は、さらに保存することができます。ボイドポインタをキャストする同様のことを行うことができるかもしれません-しかし、ユニオンはタイプだけでなく、安全。このような節約は大したことではありませんが、この構造体のすべてのインスタンスに使用されるメモリの3分の1を節約しています。
おそらく、さまざまなサイズのメッセージを送信するメッセージプロトコルで、このタイプの柔軟な構造が必要になる特定の状況を考えるのは困難ですが、それでもプログラマーにとって使いやすい代替手段があるでしょう。
ユニオンは他の言語のバリアント型に少し似ています-一度に1つのものしか保持できませんが、その宣言方法によっては、int、floatなどになります。
例えば:
typedef union MyUnion MYUNION;
union MyUnion
{
int MyInt;
float MyFloat;
};
MyUnionには、int OR float 最近設定したものに応じてのみが含まれます。これを行う:
MYUNION u;
u.MyInt = 10;
uは現在、10に等しいintを保持しています。
u.MyFloat = 1.0;
uは現在、1.0に等しいフロートを保持しています。もはやintを保持しません。当然、printf( "MyInt =%d"、u.MyInt);を実行しようとすると、特定の動作はわかりませんが、おそらくエラーが発生します。
ユニオンのサイズは、最大のフィールド(この場合はfloat)のサイズによって決まります。
ユニオンは、ハードウェア、デバイス、またはネットワークプロトコルで定義された構造体をモデル化する場合、または大量のオブジェクトを作成してスペースを節約する場合に使用されます。ただし、95%の時間は本当に必要ありません。デバッグしやすいコードを使用してください。
これらの回答の多くは、あるタイプから別のタイプへのキャストを扱っています。同じタイプのユニオンをより多く使用します(シリアルデータストリームを解析するときなど)。それらにより、framedパケットの解析/構築が簡単になります。
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..
}
}
編集エンディアンと構造体のパディングに関するコメントは有効であり、大きな懸念事項です。私はこのコード本体をほぼ完全に組み込みソフトウェアで使用しましたが、そのほとんどがパイプの両端を制御していました。
COMインターフェイスで使用される VARIANT
はどうですか? 「type」と「type」フィールドに応じて処理される実際の値を保持する共用体の2つのフィールドがあります。
組み込みデバイス用にコーディングしていたときに、ユニオンを使用しました。 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. */
};
これを見てください: X.25バッファコマンド処理
多くの可能なX.25コマンドの1つがバッファに受信され、すべての可能な構造のUNIONを使用してその場で処理されます。
学校では、次のような組合を使用しました。
typedef union
{
unsigned char color[4];
int new_color;
} u_color;
色をより簡単に処理するために>>および<<演算子を使用する代わりに、char配列の異なるインデックスを使用する必要がありました。
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];};
コンパイラは、基本的に構造体のサイズ(および場合によってはアライメント)のテーブルと、構造体のメンバーの名前、型、およびオフセットの個別のテーブルを生成します。コンパイラは、どのメンバーがどの構造に属しているかを追跡せず、タイプとオフセットが一致した場合にのみ、2つの構造が同じ名前のメンバーを持つことを許可します(struct x
およびstruct y
のメンバーq
と同様)。 pが任意の構造型へのポインターである場合、p-> qは、ポインターpに「q」のオフセットを追加し、結果のアドレスから「int」をフェッチします。
上記のセマンティクスを考えると、関数で使用されるすべてのフィールドが問題の構造内の有用なフィールドと並んでいる限り、複数の種類の構造でいくつかの有用な操作を交換可能に実行できる関数を書くことができました。これは便利な機能であり、Cを変更して、問題の構造のタイプに対する構造アクセスに使用されるメンバーを検証すると、同じアドレスに複数の名前付きフィールドを含むことができる構造がないため、Cを失うことになります。 「ユニオン」タイプをCに追加すると、そのギャップをいくらか埋めることができました(そうではありませんでしたが、IMHOもそうであったはずです)。
そのギャップを埋める組合の能力の本質的な部分は、組合員へのポインターをその組合員を含む組合へのポインターに変換でき、組合へのポインターを組合員へのポインターに変換できるという事実でした。 C89標準では、T*
を直接U*
にキャストすることは、T
とU
の両方を含む任意の共用体型へのポインターにキャストし、それをU*
にキャストすることと明確に述べていませんが、後者のキャストシーケンスの動作は定義されていません使用される共用体タイプの影響を受けます。また、標準では、T
からU
への直接キャストに反対のセマンティクスが指定されていません。さらに、関数が未知のOriginのポインターを受け取った場合、T*
を介してオブジェクトを書き込み、T*
をU*
に変換し、U*
を介してオブジェクトを読み取る動作は、タイプT
のメンバーを介してユニオンを書き込むことと同等ですタイプU
として読み取ります。これは、いくつかの場合(たとえば、共通の初期シーケンスメンバにアクセスするとき)に標準定義され、残りは実装定義(Undefinedではなく)になります。プログラムがユニオン型の実際のオブジェクトでCISの保証を悪用することはまれでしたが、未知のOriginのオブジェクトへのポインターがユニオンメンバーへのポインターのように動作し、それに関連する動作保証を持たなければならないという事実を悪用することははるかに一般的でした。
組合は素晴らしいです。私が見た共用体の賢い使い方の1つは、イベントを定義するときにそれらを使用することです。たとえば、イベントが32ビットであると判断する場合があります。
さて、その32ビット内で、イベントの送信者の識別子として最初の8ビットを指定することができます...イベント全体を処理する場合もあれば、イベントを分析してコンポーネントを比較する場合もあります。組合はあなたに両方を行う柔軟性を与えます。
unionイベント { unsigned long eventCode; unsigned char eventParts [4]; };
シンプルで非常に便利な例は...
想像してみてください:
uint32_t array[2]
があり、バイトチェーンの3番目と4番目のバイトにアクセスする場合。 *((uint16_t*) &array[1])
を実行できます。しかし、これは厳密なエイリアシング規則を残念ながら破ります!
ただし、既知のコンパイラを使用すると、次のことができます。
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
技術的には、これはまだルール違反です。しかし、すべての既知の標準がこの使用法をサポートしています。