C言語入門|配列・構造体・共用体のメモリ配置とアクセス

ここまで配列やポインタをたくさん使ってきましたが、
「実際にメモリの中でどう並んでいるのか」を
ちゃんと意識したことはありますか?

C言語では、

  • 配列
  • 構造体
  • 共用体

これらは見た目が似ていても、メモリ上の姿はまったく違う存在です。
今回は、1バイト単位のマス目の図を使って、
「どこに、何が、どれだけ置かれているのか」を可視化しながら見ていきます。

まずは配列のメモリ配置から

配列は、C言語の中でもっとも素直なメモリ構造を持っています。

サンプルプログラム(配列)

プロジェクト名:10-5-1 ソースファイル名: sample10-5-1.c

#include <stdio.h>

int main(void)
{
    int exp[3] = {10, 20, 30};

    printf("exp[0]=%d\n",exp[0]);
    printf("exp[1]=%d\n", exp[1]);
    printf("exp[2]=%d\n", exp[2]);

    return 0;
}

実行結果

exp[0]=10
exp[1]=20
exp[2]=30

配列のメモリ配置を1バイト単位で見る

ここでは、次の前提で考えます。

  • 1マス(セル)=1バイト
  • int型=4バイト
  • exp[3] は int が3個 → 合計12バイト

配列 scores のメモリ配置例

アドレスと要素の対応

要素使用する番地
exp[0]1000~1003
exp[1]1004~1007
exp[2]1008~1011

配列は、

同じ型の要素が、隙間なく、順番に並ぶ

という、とても分かりやすい構造をしています。

なぜ [] 演算子で配列にアクセスできるのか

ここで、これまで学んできた「特殊構文」を思い出します。

配列アクセスの正体

  • 配列名は、先頭要素のアドレスに変換される。
  • [] 演算子は、ただのメモリアクセス

つまり、

exp[0]

は、内部的には

*(exp + 0)

として評価され、

  • scores → 1000番地
  • +0 → そのまま
    → 1000番地に int 型変数を生み出す

という流れで処理されています。

構造体のメモリ配置を見てみよう

次は構造体です。
構造体は「連続して確保されることが多い」ですが、
必ずしも隙間なしではありません

サンプルプログラム(構造体)

プロジェクト名:10-5-2 ソースファイル名: sample10-5-2.c

#include <stdio.h>

struct Status {
    short level;
    int   hp;
};

int main(void)
{
    struct Status s = {5, 120};

    printf("level=%d, hp=%d\n", s.level, s.hp);
    return 0;
}

構造体のメモリ配置とパディング

前提

  • short型=2バイト
  • int型=4バイト

構造体 Status のメモリ配置例

詳細な割り当て

メンバ使用番地
level1008~1009
パディング1010~1011
hp1012~1015

なぜ隙間(パディング)ができるのか

これは、

  • CPUが高速にアクセスできる位置に
  • int型を配置したい

という理由によるものです。

構造体は、

見た目どおりに並ぶとは限らない

という点が重要です。

共用体のメモリ配置はまったく別物

最後に共用体です。
構造体と名前は似ていますが、考え方は正反対です。

サンプルプログラム(共用体)

プロジェクト名:10-5-3 ソースファイル名: sample10-5-3.c

#include <stdio.h>

union Status {
    short level;
    int   hp;
};

int main(void)
{
    union Status u;

    u.hp = 200;
    printf("hp=%d\n", u.hp);

    u.level = 3;
    printf("level=%d\n", u.level);

    return 0;
}

共用体のメモリ配置

共用体では、

すべてのメンバが、同じメモリ領域を共有

します。

共用体 Status のメモリ配置例

メンバごとの対応

メンバ使用番地
level1008~1009
hp1008~1011

同じ番地を使うため、

  • hp に代入したあと
  • level を読むと

意味の違う値として解釈されることになります。

共用体が使われる主な場面

共用体は、同じメモリ領域を複数の型で使い回したいときに使われます。

代表的な用途は次のとおりです。

  • メモリ容量が厳しい環境
    ・組み込み機器やマイコン
    ・RAM が数 KB〜数十 KB しかない環境

ポイントは
同時に複数のメンバを使わないことが前提、という点です。

現在の共用体の使用状況

結論から言うと、使用頻度はかなり下がっています

理由は次のとおりです。

  • メモリが潤沢になった
  • 可読性・安全性が低い
  • 型の解釈ミスがバグや未定義動作につながりやすい

そのため現在では、

  • 業務アプリケーション
  • 一般的なCプログラム

では、構造体や別変数で書く方が主流です。

配列・構造体・共用体の違いまとめ

種類メモリの使い方
配列同じ型が順番に並ぶ
構造体メンバごとに別領域(隙間あり)
共用体全メンバが同じ領域を共有

この違いを理解しておくと、

  • ポインタ操作
  • 関数引数
  • バイナリデータ処理

が一気に読みやすくなります。