共用体

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索
共用体は、同一のメモリ領域に異なる型のデータを格納できる

共用体(きょうようたい、union)は、プログラミング言語におけるデータ型の一つで、同じメモリ領域を複数の型が共有する構造である。

例として、ある入力が数字の場合は数値として、そうでない場合は文字列のまま保持したいという場合を考える。この場合、数値用と文字列用の領域をそれぞれ用意するのが一つの解法だが、入力は数値か文字列のどちらか一方なので、片方しか使われず無駄が出る。そこで代わりに、格納用の領域を一つだけ用意して、これを数値である、文字列であると場合により解釈し分けることで領域の無駄が抑えられる。この「格納用の領域」こそが共用体である。

共用体から意味のある値を取り出すためには、中身のデータそのものに加えて「今、何の型のデータが入っているか」という情報(タグという)が必要となる。タグを付加情報として持ち、常に正しい型でデータを得られるように設計された共用体を特にタグ付き共用体という。

一方で、タグの付いていない共用体の場合は、正しい型でアクセスすることは利用者側の責任である。利用者は何らかの方法で共用体に今何が入っているかを管理しなければならない。誤った型でアクセスした場合、例えば数値の入った共用体から文字列を取り出そうとして得られた値は大抵は無意味か不正なものとなる。ただし、敢えて格納時と異なる型で値にアクセスすることで、一つのバイト列に対して複数の型で解釈するテクニックもある。例としては、ある整数型の値が格納された共用体に、より小さな整数型が格納されているものとしてアクセスすることで、元々の長い整数の上位(下位)バイト部分を取り出すことができる。このテクニックは実際にはエンディアンなど環境に強く依存し、移植性は低い。

C[編集]

C言語は(タグなし)共用体をサポートしている。Cの共用体は全てのメンバのオフセットが0である(つまり先頭バイトから始まる)構造体であり、宣言に予約語structではなく共用体を意味するunionを使うことを除いて構造体と全く同じ構文で宣言・定義される。またメンバへのアクセスも構造体と同様に.演算子と->演算子で行える。共用体全体のサイズは少なくともメンバの中で最大のものを格納できる大きさに決められる(後ろにパディングされる可能性がある)。

共用体の初期化は、先頭で宣言したメンバの型で行わなければならない。

構造体を含む共用体については配置について特別な規定があり、共用体のメンバとして、先頭のメンバの型が同じである構造体を複数持つ場合は、それらの構造体の先頭メンバに限り、正確に重なり合うことが保証される。つまり、先頭メンバは別の構造体の先頭メンバの名前でも正しく参照できる。この仕様は似たような構成の構造体を集めた共用体で、先頭メンバをタグ情報として使うために役立つ。

#include <stdio.h>
#include <string.h>

union U  /* 共用体Uを定義 */
{
  double x;
  int y;
  char z[10];
  /* double, int, char[10]のいずれかを格納できる */
};

union Hoge  /* 共用体Hogeを定義:構造体の共用体 */
{
  struct Foo{
    int ifoo;
    double dfoo;
  } foo; /* 構造体Fooとメンバfooの定義 */
  struct Bar{
    int ibar;
    void *pbar;
  } bar; /* 構造体Barとメンバbarの定義 */
}; /* FooとBarの先頭メンバは同じ型(int) */

/* 共用体を使ってみる */
int main(void)
{
  union U u = { 100.0 };      /* 共用体変数の宣言と初期化 */
                              /* 先頭メンバの型(double)で初期化しなければならない */
  union Hoge hoge;
  struct Foo f = { 3, 0.14 }; /* ※注:C++では「Hoge::Foo f = ...;」とする */

  u.y = 42;                   /* int型の値を書き込む */
  strcpy(u.z, "UnionTest");   /* charの配列を書き込む */

  /* u.y += 100; */           /* 不正:charの配列が入ったuをint型として扱っている */

  hoge.foo = f;               /* Foo構造体を書き込む */
  hoge.bar.ibar = 9;          /* OK:Barの先頭メンバとしてアクセスしても良い */
  /* hoge.bar.pbar = NULL; */ /* これはダメ:今hogeに入っているのはあくまでFoo型 */

  return 0;
}

C++[編集]

C++の共用体はクラスの一種で、Cの共用体の機能に加え、メンバ関数を持てるなど機能が追加されている。ただし普通のクラスに比べ以下のような制約が加わっている。

  • 継承の機能がない。
    • 基底クラスを持つことができない。
    • 共用体から派生することはできない。
    • 仮想関数を持てない。
  • 静的メンバを持てない。
  • PODでないクラスのオブジェクト型のメンバを持てない[1]
  • 参照型のメンバを持てない。

これらの制約の一部はC++11で撤廃された。

また、C++では共用体タグ名[2]を省略して定義することで、スコープを形成しない無名の共用体を作れるようになった。

先の制約のために、標準ライブラリのものも含めてほとんどのクラスは共用体に格納できない。これは共用体がタグ情報を持たないために、コンストラクタデストラクタを正しく実行するコードを自動生成できないからである。そのため、C++で共用体が積極的に利用されることは少ない。

なお、共用体に類似したC++の機能にreinterpret_castがあり、バイト列再解釈の用途にはこちらが用いられることが一般的である。

#include <iostream>
#include <string>

union U  // 共用体Uを定義
{
private:
  double x;
  int y;
  //std::string z; // 不正:PODでないクラスはメンバにできない
  std::string *z; // ポインタは可
public:
  // メンバ関数を持てる
  U(double d) : x(d) { std::cout << "U(double)" << std::endl; }
  U(int i) : y(i) { std::cout << "U(int)" << std::endl; } 
  ~U() { std::cout << "~U()" << std::endl; }
  double getDouble() { return x; }
  int getInt() { return y; }
};

int main(void)
{
  U u1(3.14), u2(22); // コンストラクタによる共用体の構築

  std::cout << u1.getDouble() << std::endl; // メンバ関数呼び出し
  std::cout << u2.getInt() << std::endl;
  //std::cout << u2.getDouble() << std::endl; // ※何が起こるかわからない

  union { // 無名共用体
    int i;
    char c;
  };
  i = 777; // メンバは外から見える
  c = 'X';

  std::cout << c << std::endl;
  //std::cout << i << std::endl; // ※何が起こるかわからない

  return 0;
}

脚注[編集]

  1. ^ 全ての共用体メンバは必ずPODだが、共用体自身がPODになるとは限らない。共用体はユーザー定義のコンストラクタなどを持てるからである。
  2. ^ 共用体の型名を決める識別子(union xxx {...};xxx)のことで、冒頭の説明にある「タグ情報」とは別物。

出典[編集]

関連項目[編集]