同じアドレスに複数のデータを配置する共用体

こんにちは、めのんです!

今回は共用体について解説することにします。
前回、malloc関数やfree関数の簡単な実装例を見ていくと予告しましたが、そこでおそらく共用体が必要になると思いますので今やっておこうと思いました。

共用体とは?

Cの共用体というのは見た目は構造体にソックリです。
構造体はメンバを順にメモリ上に並べて配置するので、それぞれのメンバのアドレスは異なります。
ところが共用体はすべてのメンバを同じアドレスに配置します。
ですので、同時に有効になるのはどれかひとつのメンバだけです。

PHPでも8.0から共用体が導入されるようですが、Cの共用体とは似ても似つかない物ですので注意してくださいね。
どちらの共用体も主な用途は共通していると思います。

共用体の定義

共用体の定義方法は構造体にソックリです。
具体例を挙げますね。

union sample
{
  int a;
  long b;
  double c;
  char d[32];
};

構造体では「struct」だったところが「union」になっているだけで、あとはソックリです。
でもソックリなのは見た目だけです。
上の例ではメンバa, b, c, dはすべて同じアドレスに配置されます。

これはどういうことかというと、メンバaに何か値を代入したあとでメンバbに書き込むと、先にメンバaに書き込んでいた値は破壊されてしまいます。

共用体のタグ

共用体の定義でunionのあとに書く名前(上のサンプルコードではsample)は構造体と同じで「タグ」になります。
タグの名前はタグ名前空間に属すことになるのも構造体と同じです。

共用体の型名はunion タグ名になります。
上のサンプルコードではunion sampleが型名になります。
これも構造体と同じですね。

共用体のサイズと境界調整

共用体のサイズは最大のメンバのサイズと同じになります。
さきほどのサンプルの場合、典型的な処理系であればメンバdの型char[32]のサイズ(=32バイト)になります。

もう少し厳密なことをいうと、最大サイズのメンバを格納するのに十分なサイズになります。
たとえば、さきほどのサンプルではメンバdがchar[32]型でしたが、これがchar[30]型だったとします。
最大サイズであることには変わりないのですが、境界調整の関係でほかのメンバが4バイト境界や8バイト境界に整列しないといけませんので、4や8の倍数である32バイトが共用体のサイズになります(末尾に詰め物が入ります)。

境界調整は最大の境界調整数を持つメンバと同じになります。
サイズが最大のメンバではなく境界調整数が最大のメンバです。

さきほどのunion sampleであれば、典型的な処理系ではdouble型のメンバcが最大の境界調整数になると思います。

共用体のメンバへのアクセス

共用体のメンバにアクセスする方法も構造体と同じです。
つまり、共用体のオブジェクトを使ってメンバにアクセスするときはドット演算子を使いますし、共用体型へのポインタを使ってメンバにアクセスするときは矢印演算子を使います。

union sample x;
x.a = 123;   // オブジェクトを使ってメンバにアクセス
union sample *p = &x;
p->b = 456;  // ポインタを使ってメンバにアクセス

とくに注意することもありませんので、これは簡単じゃないかと思います。

共用体の初期化子

共用体の初期化子も構造体の初期化子と同じように書けますが、複数のメンバの初期値を指定することはできません。

union sample x = { 123 };  // メンバaの初期値を指定
union sample y = { 1, 2 }; // 複数のメンバに初期値を指定することはできない。

初期化子には配列や構造体と同じように要素指示子を使うことができます。
要素指示子を使えば先頭のメンバ以外に初期値を与えることができるようになります。

union sample x = { .d = "hello" };  // 先頭以外のメンバdに初期値を与える。

いくつかの注意点

共用体を使う上で注意しないといけないことがいくつかあります。
ここでは簡単にそれらを紹介しておきます。

設定したメンバと異なるメンバを使ってアクセスしない。

共用体のメンバは同じアドレスを共用していますので、あるメンバに対して値を代入したあと、別のメンバを介して値を読み出そうとすると未定義の動作になります。

現実にはそのような使い方をしているライブラリも少なくありません。
とくにマイコン用の処理系では、そのような使い方をしたライブラリが処理系に付属してくることがあります。、
そういう場合は処理系のベンダーが動作を保証しているでしょうから使ってもかまわないと思います。

共用体は集成体ではない。

これはCの仕様についての解説を読む際に注意すべきポイントになります。

共用体は見た目は構造体とソックリですが、集成体ではないので注意が必要です。
Cでは集成体は配列と構造体だけなんです。

C++では共用体が集成体になることもあるので紛らわしいんですよね。


それでは今回の解説は以上となります。
次回は列挙体を解説できたらなと考えています。