C言語のきほん|sizeofで配列要素数を求める

配列の長さは、もう数えなくて大丈夫。sizeofを使えば、変化に強くてミスしにくいコードが書けます。

C言語で配列を扱うとき、意外と大事になるのが「配列に何個の要素が入っているか」です。
たとえば、for文で先頭から順番に表示したいとき、要素数が分からないとどこまで繰り返せばよいか判断できません。

とはいえ、配列の要素数を毎回手で書いていると、あとから初期化子の数を増やしたり減らしたりしたときに、修正漏れが起こりやすくなります。
そこで役立つのが sizeof 演算子です。

sizeof は、変数やデータ型がメモリ上で何バイト使うかを調べるための仕組みです。
この性質を使うと、配列全体の大きさを、1つ分の要素の大きさで割ることで、配列の要素数を求められます。

つまり、

  • 配列全体のサイズ
  • 配列の1要素のサイズ

この2つが分かれば、要素数を自動的に計算できるというわけです。

この考え方はとても実用的です。
なぜなら、配列の初期化子を書き換えても、要素数を表す数値を別に直さなくてよいからです。
C言語ではこのように、「人が手で数える」のではなく、「プログラムに正しく計算させる」書き方がとても大切です。

なぜ要素数が必要なのか

配列は複数の値をまとめて保存できる便利な仕組みですが、for文で順番に処理するときには「何回繰り返すか」を決める必要があります。

たとえば、次のような場面です。

  • 商品の価格一覧を順番に表示する
  • 気温データを合計する
  • テスト結果を1件ずつ処理する
  • 文字以外の数値データをまとめて扱う

このとき、要素数を間違えると問題が起こります。

状況起こること
要素数を少なく書いた配列の最後まで処理されない
要素数を多く書いた配列の範囲外を読んでしまう危険がある
初期化子を変更したのに要素数を書き換え忘れたバグの原因になる

特に、配列の範囲外にアクセスするのはC言語ではとても危険です。
意図しない値を読んだり、プログラムが不安定になったりすることがあります。
だからこそ、要素数はできるだけ自動的に求めるほうが安心です。

sizeofとは何か

sizeof は、データ型や変数が何バイトの大きさを持つかを調べる演算子です。

たとえば、環境にもよりますが、一般的には次のようなサイズになることが多いです。

対象結果のイメージ
charsizeof(char)1バイト
intsizeof(int)4バイト
doublesizeof(double)8バイト
配列sizeof(array)配列全体のバイト数

ここで大切なのは、配列に対して sizeof を使うと、配列全体のサイズになるという点です。

たとえば int 型の要素が 4 個入った配列があれば、

  • 1要素のサイズは 4バイト
  • 配列全体のサイズは 16バイト

になります。

この関係を使って、

配列全体のサイズ ÷ 1要素のサイズ

を計算すれば、要素数が分かります。

配列要素数を求める基本式

配列の要素数を求める基本の書き方は、次の形です。

sizeof(配列名) / sizeof(配列名[0])

この式の意味を整理すると、次のようになります。

意味
sizeof(配列名)配列全体のサイズ
sizeof(配列名[0])先頭要素1個分のサイズ
全体 ÷ 1個分要素数

たとえば、int 型の配列に 5 個の値が入っているなら、

  • 配列全体は 20バイト
  • 1要素は 4バイト
  • 20 ÷ 4 = 5

となり、要素数 5 が求められます。

この方法の良いところは、int 配列でも double 配列でも、型に合わせて自然に計算できることです。
要素の型が変わっても、式の形はそのままで使えます。

シンプルなプログラム例

1週間の来店者数を配列に入れて、その要素数と各日の人数を表示するプログラム例です。

ファイル名:10_5_1.c

#include <stdio.h>

int main(void)
{
    // 1週間の来店者数
    int visitors[] = {12, 15, 9, 18, 14};

    // 配列の要素数を計算
    size_t day_count = sizeof(visitors) / sizeof(visitors[0]);

    printf("登録されている日数は %zu 日です。\n", day_count);

    for (size_t i = 0; i < day_count; i++) {
        printf("%zu日目の来店者数は %d 人です。\n", i + 1, visitors[i]);
    }

    return 0;
}

実行結果例

登録されている日数は 5 日です。
1日目の来店者数は 12 人です。
2日目の来店者数は 15 人です。
3日目の来店者数は 9 人です。
4日目の来店者数は 18 人です。
5日目の来店者数は 14 人です。

このプログラムの見方

このプログラムでいちばん大切なのは、次の行です。

size_t day_count = sizeof(visitors) / sizeof(visitors[0]);

この式を分解して考えてみましょう。

sizeof(visitors)

これは配列 visitors 全体のサイズです。
visitors には int 型の値が 5 個入っています。

もし int 型が 4バイトなら、

  • 12
  • 15
  • 9
  • 18
  • 14

の5個分で、配列全体は 20バイトになります。

つまり、

sizeof(visitors)

は 20 になります。

sizeof(visitors[0])

これは配列の先頭要素、つまり visitors[0] のサイズです。
visitors[0] は int 型なので、4バイトです。

つまり、

sizeof(visitors[0])

は 4 になります。

全体を1個分で割る

したがって、

sizeof(visitors) / sizeof(visitors[0])

は、

20 ÷ 4 = 5

となり、要素数 5 が求まります。

図でイメージすると分かりやすい

配列 visitors の中身をイメージすると、次のように考えられます。

もし int が 4バイトなら、各要素は同じ大きさです。

配列全体のサイズ = 4バイト × 5個 = 20バイト
1要素のサイズ   = 4バイト
要素数         = 20 ÷ 4 = 5

このように、sizeof はメモリ上の大きさから要素数を逆算しているのだと考えると理解しやすいです。

for文と組み合わせる意味

配列の要素数が求められると、for文の条件式にそのまま使えます。

for (size_t i = 0; i < day_count; i++) {
    printf("%zu日目の来店者数は %d 人です。\n", i + 1, visitors[i]);
}

この書き方の良いところは、配列の内容が増減しても、for文の回数が自動で合うことです。

たとえば、配列を次のように変更したとします。

int visitors[] = {12, 15, 9, 18, 14, 20, 16};

この場合でも、day_count は自動で 7 になります。
for文の条件式を書き直す必要はありません。

もし手で 5 と書いていたら、配列を7個に増やしても5回しか処理されません。
逆に、配列を減らしたのに大きい数のままにしてしまうと、範囲外アクセスの危険が出ます。

なぜ size_t を使っているのか

サンプルでは、要素数を入れる変数に int ではなく size_t を使っています。

size_t day_count = sizeof(visitors) / sizeof(visitors[0]);

これは、sizeof の結果の型が size_t だからです。

size_t は、サイズや個数を表すための符号なし整数型です。
メモリサイズや配列の長さのような「0以上の値」を扱うのに向いています。

そのため、sizeof の結果を受け取る変数としては size_t を使うのが自然です。

また、printf で表示するときは、size_t 用の変換指定子として %zu を使います。

printf("登録されている日数は %zu 日です。\n", day_count);

ここもC言語らしい大事なポイントです。
型に合った表示方法を使うことで、より正確で安全なコードになります。

sizeofを使うメリット

sizeof を使って配列要素数を求める方法には、実務でも学習でも大きなメリットがあります。

修正に強い

初期化子の数を増やしても減らしても、要素数を別に書き直す必要がありません。
配列の定義に合わせて自動で計算されるため、修正漏れを防ぎやすくなります。

記述ミスを防ぎやすい

手で 5 や 10 のような数を書くと、うっかり間違えることがあります。
sizeof を使えば、配列そのものから計算するので、数え間違いが起こりにくくなります。

可読性が高い

配列の長さを「手で決めた数」ではなく、「この配列の実際の要素数」として表せます。
読み手にも意図が伝わりやすい書き方です。

型が変わっても対応しやすい

int 配列から double 配列に変わっても、

sizeof(array) / sizeof(array[0])

という形はそのまま使えます。
要素1個のサイズを自動で見てくれるため、とても便利です。

マクロで要素数を管理する方法との比較

配列要素数を扱う方法としては、マクロを使って要素数を定数として書く方法もあります。

たとえば、次のような書き方です。

#define DAY_COUNT 5
int visitors[DAY_COUNT] = {12, 15, 9, 18, 14};

これはこれで分かりやすい書き方ですが、用途によって向き不向きがあります。

sizeofを使う方法

項目内容
特徴実際の配列サイズから自動で要素数を求める
強み初期化子の数が変わっても修正が少ない
向いている場面配列の内容に合わせて自然に要素数を決めたいとき

マクロを使う方法

項目内容
特徴要素数を定数としてあらかじめ定義する
強み配列サイズをコード全体で共通利用しやすい
向いている場面要素数が設計上固定で、何度も使うとき

使い分けの考え方

初期化子の数に応じて要素数を自動で求めたいなら、sizeof がとても便利です。
一方で、配列サイズ自体が仕様として決まっていて、プログラムのいろいろな場所で共通の定数として使いたいなら、マクロも有効です。

つまり、

  • 実際の配列の中身に合わせたいなら sizeof
  • 設計上の固定値として管理したいならマクロ

という使い分けがしやすいです。

注意しておきたいポイント

sizeof で配列要素数を求める方法はとても便利ですが、使う場所には少し注意が必要です。

この式は、配列そのものに対して使うときに有効です。
配列を関数に渡すと、多くの場合は配列ではなく先頭要素へのポインタとして扱われます。
そのため、関数の引数の中で同じ式を書いても、配列全体の要素数は求められません。

たとえば学習段階では、まず次の点だけ押さえておくと十分です。

  • main関数内など、配列そのものが見えている場所では有効
  • 関数引数ではそのままでは使えないことがある
  • 便利だからこそ、どこでも同じように使えるわけではない

この点は最初につまずきやすいところですが、今は「配列本体に対して使うのが基本」と覚えておけば大丈夫です。

覚えておきたい書き方

配列要素数を求めるときは、まずこの形をしっかり覚えるのがおすすめです。

size_t count = sizeof(array) / sizeof(array[0]);

この書き方はC言語でとてもよく使われます。
特に、配列を for文 で回す場面では定番です。

たとえば別の配列でも同じです。

double temperature[] = {22.5, 23.0, 21.8};
size_t count = sizeof(temperature) / sizeof(temperature[0]);

要素の型が変わっても、配列名と先頭要素を使う形は変わりません。
この統一感のある書き方も、sizeof の魅力のひとつです。

実務でも役立つ考え方

学習中は「要素数を求める便利な書き方」として覚えることが多いですが、実務ではそれ以上の意味があります。

それは、コードの修正に強くするという考え方です。

最初は 5 個のデータだったものが、あとで 7 個、10 個と変わることはよくあります。
そのたびに、関連する場所を人の手で全部直すのは大変ですし、修正漏れの原因にもなります。

sizeof を使っておけば、配列の定義に合わせて自動で要素数が決まるので、変更に強いコードになります。
このような「あとから直しやすい書き方」を早いうちから身につけておくと、C言語の理解がぐっと深まります。

ひと目で分かる整理

最後に、今回のポイントを表で整理しておきます。

内容ポイント
sizeof の役割データ型や変数のサイズをバイト単位で求める
配列に対する sizeof配列全体のサイズになる
1要素に対する sizeofその要素1個分のサイズになる
要素数の求め方sizeof(配列名) / sizeof(配列名[0])
代表的な用途for文で配列を最後まで安全に処理する
メリット修正に強い、数え間違いを防ぎやすい、見通しがよい
注意点関数引数では同じ感覚で使えない場合がある

このテーマは一見すると小さなテクニックに見えますが、C言語らしい考え方がしっかり詰まっています。
配列とメモリ、そして安全な繰り返し処理をつなぐ大切な基本なので、サンプルを何度か書き換えながら手に馴染ませていくのがおすすめです。