
C言語の基本|sizeofで配列要素数を求める
配列の長さは手で数えない。sizeofで要素数を求めれば、変更に強く安全な繰り返し処理が書けます。
配列をfor文で処理するときは、配列に何個の要素があるのかを正しく把握する必要があります。
要素数を少なく指定すれば、一部のデータが処理されません。反対に多く指定すると、配列の範囲外へアクセスする危険があります。初期化子を増減するたびにループ回数を手作業で修正する方法では、書き換え忘れも起こりやすくなります。
この問題を解決するときに便利なのがsizeof演算子です。
sizeofを使うと、配列全体が使用するバイト数と、要素1個が使用するバイト数を調べられます。この2つの値を利用すれば、配列の実際の要素数を計算できます。
この記事では、sizeofの基本的な役割、要素数を求める式、size_tを使う理由、for文との組み合わせ、マクロとの使い分け、関数の引数で使用するときの注意点まで、順番に見ていきます。
配列の要素数が必要になる理由
配列は、同じ型のデータをまとめて保存するための仕組みです。たとえば、商品の価格を配列へ保存すると、for文を使って順番に表示したり合計したりできます。
int prices[] = {180, 250, 120, 300, 210};この配列には5個の要素があります。for文ですべての要素を処理するなら、5回繰り返す必要があります。
for (int i = 0; i < 5; i++) {
printf("%d\n", prices[i]);
}ただし、配列の値をあとから増やした場合、ループ条件の5も修正しなければなりません。
int prices[] = {180, 250, 120, 300, 210, 160, 280};配列は7要素になりましたが、for文がi < 5のままでは、後ろの2要素が処理されません。
要素数の指定を間違えると、次のような問題が起こります。
| 状況 | 起こる問題 |
|---|---|
| 実際より小さい要素数を使う | 配列の後半が処理されない |
| 実際より大きい要素数を使う | 範囲外アクセスが発生する可能性がある |
| 配列だけ変更する | ループ条件との不一致が起こる |
| 同じ要素数を複数箇所へ書く | 修正漏れが起こりやすくなる |
配列そのものから要素数を計算すれば、このような不一致を防ぎやすくなります。
sizeof演算子とは
sizeofは、データ型や変数がメモリ上で使用する大きさを、バイト単位で求める演算子です。
sizeof(int)
sizeof(double)
sizeof(prices)
sizeof(prices[0])一般的な環境では、各型のサイズは次のようになることが多いです。
| 対象 | 記述例 | よくある結果 |
|---|---|---|
| char型 | sizeof(char) | 1バイト |
| int型 | sizeof(int) | 4バイト |
| double型 | sizeof(double) | 8バイト |
| 配列 | sizeof(prices) | 配列全体のバイト数 |
| 配列の要素 | sizeof(prices[0]) | 要素1個分のバイト数 |
ただし、intやdoubleのサイズは処理系によって異なる可能性があります。特定のバイト数を決めつけるのではなく、sizeofで実際のサイズを調べることが大切です。
charについては、C言語の規則によりsizeof(char)は必ず1になります。
図1:sizeofが調べる2種類のサイズ

この図から分かること
配列に対するsizeofは、配列全体のバイト数を求めます。一方、配列の1要素に対するsizeofは、その要素1個分のバイト数を求めます。
同じ型の要素は同じ大きさなので、配列全体のサイズを要素1個分のサイズで割れば、配列に含まれる要素の個数が分かります。
sizeofで配列要素数を求める基本式
配列の要素数は、次の式で求められます。
sizeof(配列名) / sizeof(配列名[0])それぞれの式には、次の意味があります。
| 式 | 求められるもの |
|---|---|
| sizeof(配列名) | 配列全体のバイト数 |
| sizeof(配列名[0]) | 先頭要素1個分のバイト数 |
| 全体のサイズ ÷ 1要素のサイズ | 配列の要素数 |
たとえば、int型の要素を5個持つ配列を考えてみましょう。
int prices[] = {180, 250, 120, 300, 210};仮にint型が4バイトなら、計算は次のようになります。
| 調べるもの | 計算 | 結果 |
|---|---|---|
| 配列全体 | 4バイト × 5要素 | 20バイト |
| 先頭要素 | 4バイト × 1要素 | 4バイト |
| 要素数 | 20 ÷ 4 | 5要素 |
したがって、次の式の結果は5になります。
sizeof(prices) / sizeof(prices[0])重要なのは、int型が必ず4バイトだから計算できるわけではないという点です。sizeofが配列全体と1要素の実際のサイズを調べるため、要素の型や環境に合った計算ができます。
なぜ先頭要素を使うのか
基本式では、1要素分のサイズを求めるために配列名[0]を使います。
sizeof(prices[0])配列はすべての要素が同じ型なので、どの要素を使ってもサイズは同じです。
sizeof(prices[0])
sizeof(prices[1])
sizeof(prices[2])これらはすべてint型1個分のサイズになります。
それでも配列名[0]がよく使われるのは、先頭要素を表すため分かりやすく、添字が必ず有効だからです。要素数が1個以上ある配列なら、配列名[0]は必ず存在します。
要素の型を直接指定する書き方もできます。
sizeof(prices) / sizeof(int)ただし、配列の型をあとからdoubleなどへ変更した場合、sizeof(int)も修正しなければなりません。
double prices[] = {180.5, 250.0, 120.8};配列名[0]を使っておけば、要素型を変更しても式を書き換えずに済みます。
sizeof(prices) / sizeof(prices[0])この書き方なら、配列自身の型に合わせて1要素分のサイズが決まります。
size_tで要素数を受け取る
sizeofの計算結果はsize_t型になります。そのため、配列の要素数を保存する変数にもsize_tを使うのが自然です。
size_t item_count = sizeof(prices) / sizeof(prices[0]);size_tは、メモリのサイズやデータの個数を表すために用意された符号なし整数型です。サイズは負の値にならないため、この用途に適しています。
size_t型の値をprintfで表示するときは、変換指定子%zuを使います。
printf("要素数は%zu個です。\n", item_count);| 内容 | 使用するもの |
|---|---|
| sizeofの結果を保存する型 | size_t |
| size_tをprintfで表示する指定子 | %zu |
| for文のインデックス変数 | size_tを使用可能 |
for文の変数にもsize_tを使えば、要素数を保存した変数と同じ型で比較できます。
for (size_t i = 0; i < item_count; i++) {
printf("%d\n", prices[i]);
}配列の要素数と内容を表示する
6回分の荷物重量を順番に表示するプログラム
ファイル名:10_5_1.c
#include <stdio.h>
int main(void)
{
/* 6回分の荷物重量を保存する配列 */
int weights[] = {8, 12, 5, 15, 9, 11};
/* 配列全体のサイズを1要素分のサイズで割る */
size_t weight_count = sizeof(weights) / sizeof(weights[0]);
printf("登録されている荷物は%zu個です。\n", weight_count);
for (size_t i = 0; i < weight_count; i++) {
printf("%zu個目の重量は%dkgです。\n", i + 1, weights[i]);
}
return 0;
}実行結果の例
登録されている荷物は6個です。
1個目の重量は8kgです。
2個目の重量は12kgです。
3個目の重量は5kgです。
4個目の重量は15kgです。
5個目の重量は9kgです。
6個目の重量は11kgです。プログラムの流れを詳しく見てみよう
最初に、荷物の重量を保存する配列weightsを初期化しています。
int weights[] = {8, 12, 5, 15, 9, 11};要素数は省略されていますが、初期化子が6個あるため、コンパイラによって6要素の配列として扱われます。
次の行で、配列の要素数を求めています。
size_t weight_count = sizeof(weights) / sizeof(weights[0]);仮にint型が4バイトの場合、それぞれの結果は次のようになります。
sizeof(weights) → 24
sizeof(weights[0]) → 4
24 ÷ 4 → 6計算結果の6がweight_countへ代入されます。
要素数の表示では、size_t用の%zuを使用しています。
printf("登録されている荷物は%zu個です。\n", weight_count);for文では、weight_countを継続条件に利用しています。
for (size_t i = 0; i < weight_count; i++) {
printf("%zu個目の重量は%dkgです。\n", i + 1, weights[i]);
}iは0から始まり、5まで変化します。これにより、weights[0]からweights[5]までの全要素が1回ずつ処理されます。
画面に表示する個数は1から始めたほうが自然なので、表示用にはi + 1を使っています。配列の添字には、そのままiを使います。
| iの値 | 表示する番号 | 処理する要素 |
|---|---|---|
| 0 | 1個目 | weights[0] |
| 1 | 2個目 | weights[1] |
| 2 | 3個目 | weights[2] |
| 3 | 4個目 | weights[3] |
| 4 | 5個目 | weights[4] |
| 5 | 6個目 | weights[5] |
図2:配列のバイト数から要素数を計算する流れ

この図から分かること
配列weightsにはint型の値が6個入っています。int型が4バイトの環境では、配列全体のサイズは24バイトです。
1要素分の4バイトで割ると、24 ÷ 4によって6という要素数が求められます。実際のプログラムでは、バイト数を手で決めず、両方をsizeofで調べることがポイントです。
配列を変更してもfor文を直さずに済む
sizeofで求めた要素数をfor文で使用すると、配列の初期化子を変更したときにもループ回数が自動的に合います。
たとえば、先ほどの配列へ2個の値を追加してみます。
int weights[] = {8, 12, 5, 15, 9, 11, 7, 14};初期化子が8個になったため、weightsは8要素の配列になります。
size_t weight_count = sizeof(weights) / sizeof(weights[0]);この式の計算結果も自動的に8になります。for文はweight_countを使っているため、継続条件を修正する必要はありません。
for (size_t i = 0; i < weight_count; i++) {
printf("%d\n", weights[i]);
}この書き方には、次の利点があります。
- 初期化子を追加してもループ回数が自動で増える
- 初期化子を減らしてもループ回数が自動で減る
- 要素数の数え間違いを防ぎやすい
- 配列とfor文の不一致が起こりにくい
- 範囲外アクセスを防ぎやすくなる
配列を変更したときに修正する場所が少なくなるため、保守しやすいコードになります。
int以外の配列でも同じ式を使える
基本式は、int型の配列だけに使うものではありません。
sizeof(配列名) / sizeof(配列名[0])double型の配列でも、同じ形で要素数を求められます。
double temperatures[] = {21.5, 23.0, 19.8, 22.6};
size_t count =
sizeof(temperatures) / sizeof(temperatures[0]);char型の配列でも考え方は同じです。
char grades[] = {'A', 'B', 'A', 'C'};
size_t count = sizeof(grades) / sizeof(grades[0]);型によって1要素のバイト数は異なりますが、全体を1要素分で割るという関係は変わりません。
| 配列の型 | 配列全体 | 1要素分 | 計算結果 |
|---|---|---|---|
| int型5要素 | sizeof(numbers) | sizeof(numbers[0]) | 5 |
| double型4要素 | sizeof(values) | sizeof(values[0]) | 4 |
| char型6要素 | sizeof(codes) | sizeof(codes[0]) | 6 |
配列の要素型を直接式へ書かないため、型の変更にも対応しやすい書き方です。
マクロで要素数を管理する方法との違い
配列の要素数は、マクロを使って管理することもできます。
#define WEIGHT_COUNT 6
int weights[WEIGHT_COUNT] = {8, 12, 5, 15, 9, 11};sizeofを使う方法とマクロを使う方法には、それぞれ向いている場面があります。
| 比較項目 | sizeofで計算 | マクロで管理 |
|---|---|---|
| 要素数の決まり方 | 実際の配列から計算する | 定義した固定値を使う |
| 初期化子の増減 | 自動的に対応する | マクロも確認する必要がある |
| 配列サイズの共有 | 配列が見える場所で使う | プログラム内の複数箇所で使いやすい |
| 向いている場面 | 初期値の個数から長さを決める | 仕様として個数が固定されている |
| 主な注意点 | 関数引数では使えない場合がある | 配列定義との不一致に注意する |
初期化子の個数に合わせて配列の長さを決めたい場合は、sizeofによる計算が便利です。
一方、入力を必ず10件受け付けるなど、配列サイズが設計上の決まりとして存在する場合は、マクロで管理する方法も分かりやすい書き方です。
配列本体に対して使うことが重要
要素数を求める式は、配列そのものに対してsizeofを使える場所で有効です。
int values[] = {10, 20, 30, 40};
size_t count = sizeof(values) / sizeof(values[0]);このvaluesは配列本体なので、sizeof(values)は配列全体のサイズになります。
ところが、配列を関数の引数として受け取ると、引数は実質的に先頭要素を指すポインタとして扱われます。
void show_values(int values[])
{
/* ここでsizeof(values)を使っても、
配列全体のサイズにはならない */
}この場所にあるvaluesは、配列全体ではなくポインタとして扱われます。そのため、次の式から元の配列の要素数を正しく求めることはできません。
sizeof(values) / sizeof(values[0])関数へ配列を渡す場合は、要素数も別の引数として渡す方法が基本です。
void show_values(const int values[], size_t count)ただし、この関数を使った完全なプログラムは今回の範囲には含めず、ここでは注意点として考え方だけ押さえておきましょう。
図3:配列本体と関数引数で異なるsizeofの結果

この図から分かること
sizeofによる要素数の計算が正しく働くのは、配列本体に対してsizeofを使える場合です。
関数の配列引数はポインタとして扱われるため、sizeofで得られるのは配列全体ではなく、ポインタのサイズです。関数へ配列を渡すときは、要素数も一緒に渡す必要があります。
sizeofを使うときの確認ポイント
| 確認すること | ポイント |
|---|---|
| 対象は配列本体か | ポインタでは配列全体のサイズを求められない |
| 分母は1要素のサイズか | sizeof(array[0])を使う |
| 結果を保存する型は適切か | size_tを使う |
| printfの指定子は適切か | size_tには%zuを使う |
| for文の条件は正しいか | i < 要素数とする |
| 配列の型を直接書いていないか | sizeof(array[0])なら型変更に対応しやすい |
覚えておきたい基本形は、次の2行です。
size_t count = sizeof(array) / sizeof(array[0]);
for (size_t i = 0; i < count; i++) {
/* array[i]を処理する */
}配列本体から実際の要素数を求め、その結果をfor文の条件に使うことで、配列の増減に自然に対応できます。要素数を人が数えて管理する箇所を減らせるため、修正漏れや範囲外アクセスを防ぎやすいコードになります。
