ユニオンはいつ使用する必要がありますか? なぜそれらが必要なのですか?
20 に答える
ユニオンは、整数と浮動小数点数のバイナリ表現を変換するためによく使用されます。
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;
もちろん、エンディアン (バイト順) とプロセッサ アーキテクチャは重要です。
もう 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;
低レベルのシステム プログラミングは妥当な例です。
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
接続の「クラス」を上記のいずれかにする場合は、次のように記述できます。
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 値ではなく、1 つの 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.
次の言語要素で構成されます。
- シンボル[セット]
- 変数[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;
次に、各要素のサイズは最大ユニオンのサイズでした (typ の場合は 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
.
ユニオンを使用すると、サイズを 64 ビットまたは 8 バイトに減らすことができます。
struct variant {
int type;
union {
double number;
char *string;
} value;
};
さまざまな変数タイプなどを追加したい場合は、さらに節約できます。void ポインターをキャストして同様のことを行うことができるのは事実かもしれませんが、ユニオンを使用すると、型だけでなく、よりアクセスしやすくなります。安全。このような節約は大したことではないように思えますが、この構造体のすべてのインスタンスに使用されるメモリの 3 分の 1 を節約しています。
おそらく、さまざまなサイズのメッセージを送信するメッセージプロトコルで、このタイプの柔軟な構造が必要になる特定の機会を考えるのは難しいですが、それでもおそらく、より優れた、よりプログラマーに優しい代替手段があります。
ユニオンは他の言語のバリアント型に少し似ています。一度に保持できるのは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に等しいfloatを保持します。それはもはやintを保持しません。明らかに今、printf( "MyInt =%d"、u.MyInt);を実行しようとすると 具体的な動作はわかりませんが、エラーが発生する可能性があります。
ユニオンのサイズは、最大のフィールド(この場合はフロート)のサイズによって決まります。
これらの回答の多くは、ある型から別の型へのキャストを扱っています。私は、同じタイプの共用体をより多く使用します (つまり、シリアル データ ストリームを解析する場合)。それらは、フレーム化されたパケットの解析/構築を簡単にすることができます。
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..
}
}
編集 エンディアンと構造体のパディングに関するコメントは有効であり、大きな懸念事項です。私はこのコード本体をほぼ完全に組み込みソフトウェアで使用してきましたが、そのほとんどはパイプの両端を制御していました。
ユニオンは、ハードウェア、デバイス、またはネットワーク プロトコルによって定義された構造体をモデル化する場合、または多数のオブジェクトを作成してスペースを節約する場合に使用されます。95% の確率でそれらを必要とするわけではありませんが、デバッグしやすいコードを使用してください。
学校では、次のようなユニオンを使用しました。
typedef union
{
unsigned char color[4];
int new_color;
} u_color;
>> および << 演算子を使用する代わりに、色をより簡単に処理するために使用しました。char 配列の別のインデックスを使用するだけで済みました。
組合は素晴らしいです。私が見たユニオンの巧妙な使用法の1つは、イベントを定義するときにそれらを使用することです。たとえば、イベントが32ビットであると判断する場合があります。
さて、その32ビット内で、イベントの送信者の識別子として最初の8ビットを指定したいと思うかもしれません...イベント全体を扱う場合もあれば、イベントを分析してそのコンポーネントを比較する場合もあります。ユニオンは、両方を実行する柔軟性を提供します。
ユニオンイベント {{ unsigned long eventCode; unsigned char eventParts [4]; };
VARIANT
COMインターフェイスで使用されているのはどうですか?「タイプ」フィールドと、「タイプ」フィールドに応じて処理される実際の値を保持する共用体の 2 つのフィールドがあります。
組み込みデバイスのコーディングを行っていたときは、union を使用しました。16 ビット長の C int があります。また、EEPROM からの読み取り/EEPROM への保存が必要な場合は、上位 8 ビットと下位 8 ビットを取得する必要があります。だから私はこのように使用しました:
union data {
int data;
struct {
unsigned char higher;
unsigned char lower;
} parts;
};
シフトする必要がないため、コードが読みやすくなります。
一方、stl アロケータにユニオンを使用する古い 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 を使用してその場で処理されます。
シンプルで非常に便利な例は....
想像:
がありuint32_t array[2]
、バイトチェーンの 3 番目と 4 番目のバイトにアクセスしたいとします。あなたがすることができます*((uint16_t*) &array[1])
。しかし、これは悲しいことに、厳密なエイリアシング規則を破っています!
ただし、既知のコンパイラを使用すると、次のことができます。
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
技術的には、これは依然として規則違反です。しかし、既知の標準はすべてこの使用法をサポートしています。