複数のメンバが同じメモリを共有するデータ型。構造体(struct)と対で理解する。
union は全メンバが同じメモリを共有#include <stdio.h> union Data { int i; float f; char s[8]; }; int main(void) { union Data d; d.i = 42; printf("d.i = %d\n", d.i); // 42 d.f = 3.14f; // 書き換えた瞬間にd.iは無効 printf("d.f = %f\n", d.f); // 3.14 printf("d.i = %d\n", d.i); // 値はfloat表現のビット列 printf("sizeof = %zu\n", sizeof(d));// 8 (一番大きいメンバ) return 0; }
#include <stdio.h> struct S { int a; int b; }; union U { int a; int b; }; int main(void) { struct S s = {10, 20}; printf("struct: a=%d b=%d size=%zu\n", s.a, s.b, sizeof(s)); // → a=10 b=20 size=8 union U u; u.a = 10; u.b = 20; printf("union: a=%d b=%d size=%zu\n", u.a, u.b, sizeof(u)); // → a=20 b=20 size=4 (aもbも同じ場所!) }
union Mix { char c; // 1バイト int i; // 4バイト double d; // 8バイト }; // sizeof(union Mix) は少なくとも 8バイト // アラインは double の要求に合わせて 8
union Split { int n; // 4バイトをまとめて struct { char b0, b1, b2, b3; } bytes; // バイト単位 }; union Split s; s.n = 0x12345678; printf("%02x %02x %02x %02x\n", (unsigned)s.bytes.b0, (unsigned)s.bytes.b1, (unsigned)s.bytes.b2, (unsigned)s.bytes.b3); // リトルエンディアンなら: 78 56 34 12 // ビッグエンディアンなら: 12 34 56 78
#include <stdio.h> int is_little_endian(void) { union { int i; char c[sizeof(int)]; } u; u.i = 1; return u.c[0] == 1; // 先頭バイトが1なら LE } int main(void) { printf("%s-endian\n", is_little_endian() ? "little" : "big"); return 0; }
01 00 00 00 → c[0] == 100 00 00 01 → c[0] == 0
int n = 1; char *p = (char*)&n; でも同じことができるが、unionの方がコンパイラが正しく扱える(ポインタ型変換より安全)とされる。sum type、Rustの enum に相当します。#include <stdio.h> enum ValueKind { V_INT, V_FLOAT, V_STRING }; struct Value { enum ValueKind kind; // 「今どれが有効か」のタグ union { int i; float f; char s[32]; } data; }; void print_value(struct Value v) { switch (v.kind) { case V_INT: printf("int: %d\n", v.data.i); break; case V_FLOAT: printf("float: %f\n", v.data.f); break; case V_STRING: printf("str: %s\n", v.data.s); break; } } int main(void) { struct Value a = {V_INT, .data.i = 42}; struct Value b = {V_FLOAT, .data.f = 3.14f}; struct Value c; c.kind = V_STRING; snprintf(c.data.s, sizeof(c.data.s), "hello"); print_value(a); // int: 42 print_value(b); // float: 3.14 print_value(c); // str: hello }
// struct Value のサイズ: // enum (4) + 最大メンバ(32) + パディング = 36以上 // 32バイトの文字列を使わない場合でも常にこのサイズになる点に注意
(int)3.14 とは違う)void*とサイズの組、あるいはC++ の std::variant / Rust の enum の方が安全。Cでも tagged union + ラッパー関数で十分使える。union { float f; uint32_t u; } を使い、f = 1.0f のときの 32bit 表現を16進数で表示せよ(IEEE 754 → 0x3F800000)。is_little_endian を自分で書き、手元の環境で実行結果を確かめよ。Mac / Linux / Windows のほとんどはリトルエンディアンのはず。struct Value を作り、Value add(Value a, Value b) を実装。数値なら合計、真偽値同士なら OR を返す。型が不一致ならエラーメッセージを出す。