Cプログラマへの道 #10 - 共用体 vs 構造体

Last Edited: 8/12/2024

このブログ記事では、C言語における共用体と構造体の違いについて紹介します。

C Union

共用体

共用体( 又はユニオン 英: union)は、C言語で異なる型の値を格納できる便利なデータ構造です。次の例をご覧ください。

union Data {
    int x;
    float y;
    char z[16]; 
};

unionの定義方法がstructと同じように見えますが、どう違うのでしょうか? structはすべての変数に対してメモリを確保しますが、 unionは最大の変数を格納するためのメモリしか確保しません。

struct Data1 {
    int x;
    float y;
    char z[16]; 
};
 
int main () {
    printf("unionのサイズ: %lu \n", sizeof(Data)); // => unionのサイズ: 16
    printf("structのサイズ: %lu \n", sizeof(Data1)); // => structのサイズ: 24
 
    Data data;
    data.x = 1;
    data.y = 3.14;
    strcpy(data.z, "my data");
    
    printf("x: %d, y: %f, z: %s", data.x, data.y, data.z);
    // => x: 1978725, y: 0, z: my data
    return 0;
}

unionchar配列のサイズ(16バイト)しかメモリを確保しませんが、structは24バイトを確保して、すべての変数が正しく格納されるようにします。 そのため、unionに値を代入すると、メモリに格納された値が書き換えられます。メモリを節約できるため、unionは1つの型だけが必要な場合や、 できるだけメモリを節約したい場合に便利です。

共用体 & 構造体

場合によっては、unionstructを効果的に組み合わせることができます。

union Color { 
    struct { float r, g, b; } rgb; 
    struct { float c, m, y, k; } cmyk; 
    struct { float h, s, l; } hsl; 
};
 
enum BufferType { Char, Float, Double };
 
struct Buffer {
    BufferType type;
    union {
        char x[1024];
        float y[1024];
        double z[1024]; 
    } data;
};

ColorBufferの例は、unionstructを適切に組み合わせて使用する方法を示しています。

構造体のパディング

構造体を定義してそのサイズを出力すると、予想以上のメモリが割り当てられていることに気づくかもしれません。

struct Data {
    char x; // 1バイト
    int y;  // 4バイト
};
 
int main () {
    Data data;
    printf("structのサイズ: %luバイト\n", sizeof(data));
    // => structのサイズ: 8バイト
    return 0;
}

intcharを合わせると5バイトしか必要ないはずですが、structのサイズは8バイトです。これは、CPUがメモリにアクセスする方法に関連しています。 CPUは1バイトずつではなく、通常4バイトの塊でメモリにアクセスします(デバイスにより異なります)。xyが5バイトしか割り当てられていない場合、 yを取得するには2回アクセスする必要があります。一方で、xyの間にパディングを追加して、それぞれが4バイトを占めるようにすると、CPUは1回の アクセスでそれぞれを取得できます。これが、可能であればunionを使用してメモリを節約したい理由の一つです。

パディングの理解は、structの構築を最適化する際にも役立ちます。

struct Data1 {
    char x;
    int y;
    char z;
};
 
struct Data2 {
    int y;
    char x;
    char z;
};
 
int main () {
    printf("Data1のサイズ: %lu \n", sizeof(Data1));
    printf("Data2のサイズ: %lu \n", sizeof(Data2));
    // => Data1のサイズ: 12
    // => Data2のサイズ: 8
    return 0;
}

Data1Data2の違いは、変数が格納される順序だけですが、それが割り当てられるメモリの量に影響を与えます。Data1では、yが中央に配置されるため、 xyの間にパディングが必要ですが、Data2では、yを最初に配置することで、xのパディングを不要にしています。

クイズ

この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。

リソース