【C言語入門】union(共用体)の使い方

共用体って使ってますか?共用体は構造体と同じように型の違うメンバを持つことができます。

ただし構造体は複数のメンバを一度に使えますが、共用体は一度に使えるメンバは1つだけです。メモリの使用を制限して型の違う変数を使用したい場合に使うと便利です。この記事では、共用体について

  • 共用体とは
  • 共用体の使い方
  • ビットフィールドの使い方

など基本的な内容から、応用的な使い方の内容についても解説していきます。今回は共用体について、使い方をわかりやすく解説します!

目次

union(共用体)とは

共用体とはデータ型の一つで、同じメモリ領域を複数のメンバが共用する構造になっています。メンバの型は違っていても構いません。共用体のサイズはもっとも大きなメンバのサイズとなります。

メンバを持つ点で構造体と似ていますが、構造体のメンバはそれぞれアドレス先が異なるのに対して、共用体のメンバは一箇所に格納されている点で異なります。それでは共用体の使い方についてみていきましょう。

共用体の使い方

共用体は型を定義し、その型を使って変数名を宣言して使用します。型は以下のように定義します。

共用体の型の定義:

union 共用体名 {
    データ型 メンバ名1;
    データ型 メンバ名2;
    データ型 メンバ名3;
    ・・・ ・・・;
};

この型を使って共用体の型の変数を宣言します。

共用体の変数名の宣言:

union 共用体名 変数名;

変数を宣言する際に初期化をすることもできます。

共用体の変数名の宣言:

union 共用体名 変数名 = {データ};

最初のメンバのみ初期化できます。また、共用体の型の定義と変数名の宣言を一緒に行うこともできます。

共用体の型の定義:

union 共用体名 {
    データ型 メンバ名1;
    データ型 メンバ名2;
    データ型 メンバ名3;
    ・・・ ・・・;
} 変数名;

この場合、共用体名を省略することもできます。共用体のメンバは共用体の変数名から「.」(ドット演算子)を使用して呼び出します。

変数のメンバの呼び出し:

変数名.メンバ名1;

基本的な使い方

それでは実際の使い方をサンプルコードで確認していきましょう。

#include <stdio.h>
 
union book {
    char name[16];
    int price;
};
 
int main(void) {
    union book bk = {"桃太郎"}; // 宣言と初期化
    printf("本の名前は%sです\n", bk.name);
    printf("共用体のサイズは%ldバイトです\n", sizeof(bk));
    
    bk.price = 500; // 別のメンバへの値の代入
    printf("本の名前は%sです\n", bk.name);
    printf("本の価格は%d円です\n", bk.price);
    
    return 0;
}

実行結果:

本の名前は桃太郎です
共用体のサイズは16バイトです
本の名前はです
本の価格は500円です

このサンプルコードでは、共用体「book」を定義しています。共用体「book」の変数「bk」を宣言し、文字列「桃太郎」で初期化しています。変数「bk」のサイズはメンバ内で最もサイズが大きい文字列「name」の16バイトであることがわかります。

その後、変数「bk」からドット演算子を使ってint型のメンバ「price」を呼び出し値を代入しています。メンバ「name」と「price」を出力表示していますが、メンバ「name」は表示されていません。

メンバ「price」に値を代入したことで、メンバが初期化時の「name」から「price」に変わっています。

ポインタを使った扱い方

共用体は構造体と同じようにポインタを使ってアドレスにアクセスすることができます。共用体のメンバのアドレスにアクセスする場合は「->」(アロー演算子)を使用します。

ポインタを使った変数のメンバの呼び出し:

変数のポインタ名->メンバ名1;

それではサンプルコードで確認していきましょう。

#include <stdio.h>
 
union book {
    char name[16];
    int price;
};
 
int main(void) {
    union book bk = {"桃太郎"}; // 宣言と初期化
    printf("本の名前は%sです\n", bk.name);
    
    union book *p; // 共用体のポインタの宣言
    p = &bk; // ポインタにunion型の変数のアドレスを代入
    p->price = 500; // ポインタを使って別のメンバへの値の代入
    printf("本の価格は%d円です\n", p->price);
    
    return 0;
}

実行結果:

本の名前は桃太郎です
本の価格は500円です

このサンプルでは共用体のポインタ「p」を宣言しています。共用体の変数「bk」のアドレスをポインタ「p」に代入し、アロー演算子を使ってメンバ「price」にアクセスしています。

ビットフィールドの使い方

ビットフィールドは、1バイトつまり8ビット以下の変数を定義するときに使用します。変数は通常1バイト以上消費します。ビットフィールドを使うことで、サイズが8ビット未満の変数を定義することができて、バイト数の消費のムダを排除することができます。

ビットフィールドは確保するビット数に応じて扱える値の範囲が変わります。確保するビット数が

  • 1ビットの場合 0~1
  • 2ビットの場合 0~3
  • 3ビットの場合 0~7

ビットフィールドは構造体を使って宣言します。ビットフィールドのメンバの型には「unsined char」もしくは「unsined int」が使われます。ビットフィールドの宣言方法は構造体の通常の宣言方法と変わりません。

それではサンプルコードで確認していきましょう。

#include <stdio.h>
 
// ビットフィールドの宣言とメンバの初期化
struct bits {
    unsigned char b1 : 1; // メンバの初期化
    unsigned char b2 : 1;
    unsigned char b3 : 1;
    unsigned char b4 : 1;
    unsigned char b5 : 1;
    unsigned char b6 : 1;
    unsigned char b7 : 1;
    unsigned char b8 : 1;
};
 
union {
    unsigned char chr;
    struct bits b;
} u;
 
int main(void) {
    u.chr = 255;
    printf("u.chrの値は%dです\n", u.chr);
    printf("u.chrのビット表示は%d%d%d%d%d%d%d%dです\n", 
            u.b.b1, u.b.b2, u.b.b3, u.b.b4, u.b.b5, u.b.b6, u.b.b7, u.b.b8);
    printf("uのサイズは%ldバイトです\n", sizeof(u));
    
    return 0;
}

実行結果:

u.chrの値は255です
u.chrのビット表示は11111111です
uのサイズは1バイトです

このサンプルコードでは構造体「bits」でビットフィールドの宣言とメンバの初期化を行っています。また、このビットフィールドをメンバに持つ共用体「u」を宣言しています。

共用体「u」は「unsined char」型のメンバを8個もつ構造体「b」をメンバに持っていますが、サイズは1バイトです。「b」の「unsined char」型のメンバのサイズは1ビットであることがわかります。

まとめ

ここでは、共用体について説明しました。共用体は複数の型をメンバに持つことができて、あとで型とメンバの値を変更したいときに使うと便利です。使いこなすことができるように、この記事を何度も参考にして下さいね!

この記事を書いた人

熊本在住のフリープログラマ兼ライターです。C/C++/C#、Java、Python、HTML/CSS、PHPを使ってプログラミングをしています。専門は画像処理で最近は機械学習、ディープラーニングにはまっています。幅広くやってきた経験を活かしてポイントをわかりやすくお伝えしようと思います。
お問合せはこちらでも受け付けています。
info@sss-lab.com

目次