回答:
共用体は、整数と浮動小数点のバイナリ表現間の変換によく使用されます。
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のスペースを占有します。ユーザが値を設定した場合、その後の値セットBは、それがの値上書きするそれらは両方とも同じメモリロケーションを共有しているからです。
たくさんの使い方。やる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バイトですが、これらは典型的な値ですが、 実際のサイズは実装によって異なります)。
「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;}
)やその他のタイプます。
基本的な前提は、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 | |
+-------+
make sure you free this later
コメントを定数要素から削除する必要がありますか?
さまざまな方法で使用される可能性のあるメモリの再利用が容易になると思います。つまり、メモリの節約です。たとえば、短い文字列と数値を保存できる「バリアント」構造体を実行したいとします。
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を節約しています。
このタイプの柔軟な構造が必要になる特定の機会を考えるのは難しいです。おそらく、異なるサイズのメッセージを送信するメッセージプロトコルの場合ですが、それでも、おそらくより優れた、よりプログラマーフレンドリーな代替手段があります。
ユニオンは他の言語のバリアント型に少し似ています-ユニオンは一度に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)のサイズによって決まります。
sizeof(int) == sizeof(float)
(== 32
)通常。
これらの回答の多くは、あるタイプから別のタイプへのキャストに対応しています。私は同じタイプのユニオンをより多く使用します(つまり、シリアルデータストリームを解析する場合)。これにより、フレーム化されたパケットの解析/構築が簡単になります。
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..
}
}
編集 エンディアンと構造体の埋め込みに関するコメントは有効であり、大きな懸念事項です。私はこのコード本体をほぼ完全に組み込みソフトウェアで使用しており、そのほとんどはパイプの両端を制御できました。
組み込みデバイスをコーディングするときに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. */
};
struct
、あなたの周りにhigher
/をlower
?現在、両方とも最初のバイトのみを指す必要があります。
これを見てください:X.25バッファーコマンドの処理
多くの可能なX.25コマンドの1つがバッファに受信され、すべての可能な構造のUNIONを使用して適切に処理されます。
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];};
コンパイラーは基本的に、構造体のサイズ(および場合によっては配置)のテーブルと、構造体のメンバーの名前、タイプ、およびオフセットの個別のテーブルを生成します。コンパイラは、(メンバーのようにメンバーがどの構造に属しているのを追跡していなかった、と二つの構造が同じ名前を持つメンバーを持つことができるようになる場合にのみ、タイプと一致オフセットq
のstruct x
とstruct y
)。pが任意の構造体型へのポインタである場合、p-> qは "q"のオフセットをポインタpに追加し、結果のアドレスから "int"をフェッチします。
上記のセマンティクスを考えると、関数によって使用されるすべてのフィールドが問題の構造内の有用なフィールドと並んでいれば、複数の種類の構造に対していくつかの有用な操作を交換して実行できる関数を作成することができました。これは便利な機能であり、問題の構造体のタイプに対する構造体アクセスに使用されるメンバーを検証するようにCを変更すると、同じアドレスに複数の名前付きフィールドを含むことができる構造体を持たせる手段がなければ、それを失うことになります。「ユニオン」タイプをCに追加することで、そのギャップをいくぶん埋めることができました(IMHOはそうではありませんでしたが、そうであったはずです)。
そのギャップを埋めるためのユニオンの機能の本質的な部分は、ユニオンメンバーへのポインターがそのメンバーを含む任意のユニオンへのポインターに変換でき、任意のユニオンへのポインターが任意のメンバーへのポインターに変換できるという事実でした。C89標準では、aにT*
直接U*
キャストすることは、それをT
andの両方を含む共用体型へのポインターにキャストすることと同等であると明確に述べていませんがU
、それをにキャストするとU*
、後者のキャストシーケンスの定義された動作は、共用体型が使用されており、標準はT
to からの直接キャストに対して逆のセマンティクスを指定していませんU
。さらに、関数が原因不明のポインターを受け取った場合、を介してオブジェクトを書き込む動作T*
、T*
へU*
を介してオブジェクトを読み取ることはU*
、型T
を介してunion viaメンバーを書き込み、型として読み取ることと同等ですU
。これは、いくつかのケースで標準定義されます(たとえば、共通初期シーケンスメンバーにアクセスする場合)および実装定義(Undefinedではなく) )残りのため。プログラムがユニオンタイプの実際のオブジェクトでCIS保証を利用することはまれでしたが、起源が不明なオブジェクトへのポインターはユニオンメンバーへのポインターのように動作し、それに関連付けられた動作保証が必要であるという事実を利用することははるかに一般的でした。
foo
がint
オフセット8の場合、anyPointer->foo = 1234;
「anyPointerでアドレスを取得し、8バイトずらして、値1234の整数ストアを結果のアドレスに実行します。コンパイラーは、anyPointer
識別されたかどうかを知る必要も、気にする必要もありません。foo
メンバーにリストされていた構造タイプ
anyPointer
structメンバーで識別されるかどうかを知らない場合、コンパイラはto have a member with the same name only if the type and offset matched
あなたの投稿のこれらの状態をどのようにチェックしますか?
p->foo
のタイプとオフセットに依存するため、コンパイラーは構造体メンバーの名前のリストを保持しますfoo
。本質的に、のp->foo
省略形でした*(typeOfFoo*)((unsigned char*)p + offsetOfFoo)
。後者の質問については、コンパイラーがstructメンバー定義を検出した場合、その名前のメンバーが存在しないか、その名前のメンバーが同じタイプとオフセットを持つ必要があります。一致しないstructメンバー定義が存在する場合、は不正確だったと思いますが、エラーの処理方法がわかりません。
シンプルで非常に便利な例は...
想像してみてください:
あなたが持っているuint32_t array[2]
とバイト・チェーンの3番目と4番目のバイトにアクセスしたいです。あなたがすることができます*((uint16_t*) &array[1])
。しかし、これは悲しいことにエイリアシングの厳密な規則に違反しています。
ただし、既知のコンパイラでは、次のことが可能です。
union un
{
uint16_t array16[4];
uint32_t array32[2];
}
技術的にはこれはまだルール違反です。しかし、すべての既知の標準はこの使用法をサポートしています。