C言語でプログラミングを始めると、最初につまずきやすいポイントの1つが配列です。
特に「添え字(index)はなぜ0から始まるのか」「なぜ範囲外アクセスが危険なのか」は、きちんと理解しておかないとバグの原因になります。
この記事では、配列の要素に番号でアクセスする基本と、添え字の正しい考え方を、C言語初心者の方にもわかりやすく説明します。
配列の要素アクセスとは
配列と要素の基本
C言語の配列は、同じ型のデータを、メモリ上に連続して並べた入れ物です。
例えば、10個のintをまとめて扱いたいときに配列を使います。
配列を宣言するときは、次のように書きます。
#include <stdio.h>
int main(void) {
// int型の値を5個まとめて保存できる配列arrを宣言
int arr[5]; // ここで5は「要素の個数(サイズ)」
// とりあえず値を代入してみる
arr[0] = 10;
arr[1] = 20;
arr[2] = 30;
arr[3] = 40;
arr[4] = 50;
// 1つずつ表示
printf("arr[0] = %d\n", arr[0]);
printf("arr[1] = %d\n", arr[1]);
printf("arr[2] = %d\n", arr[2]);
printf("arr[3] = %d\n", arr[3]);
printf("arr[4] = %d\n", arr[4]);
return 0;
}
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
ここで、配列の中の1つ1つの値を「要素」と呼びます。
要素には、それぞれ番号が振られています。
その番号が添え字(index)です。
添え字(index)とは何か
添え字とは、配列の中で「何番目の要素か」を指定するための番号です。
C言語では、添え字を[]の中に書きます。
例えばarr[3]と書くと、「arrという配列の、4番目の要素」にアクセスします。
ここで注意してほしいのは、人間の数え方では4番目ですが、添え字は3になるという点です。
これは後で詳しく説明します。
添え字は、以下のようなイメージで対応しています。
- 添え字0 → 1番目の要素
- 添え字1 → 2番目の要素
- 添え字2 → 3番目の要素
このように、添え字はプログラムの中で要素を指し示す「番号札」のようなものだと考えると理解しやすいです。
添え字とメモリ上の並びの関係
C言語の配列は、メモリ上に要素が連続して並ぶという特徴があります。
これが「添え字が0から始まる」こととも関係しています。
イメージとして、メモリを長い箱の列だと考えてください。
配列int arr[5]を宣言すると、その箱が5個分まとめて確保されます。
そして、最初の箱がarr[0]、次がarr[1]というように続きます。
概念的には次のようなイメージです。
| 添え字(index) | 要素 | メモリ上の位置のイメージ |
|---|---|---|
| 0 | arr[0] | 先頭(スタート地点) |
| 1 | arr[1] | 先頭から1つ分先 |
| 2 | arr[2] | 先頭から2つ分先 |
| 3 | arr[3] | 先頭から3つ分先 |
| 4 | arr[4] | 先頭から4つ分先 |
「先頭から何個分先か」を表すのが添え字です。
先頭は「0個分先」なので添え字は0、1つ先は「1個分先」なので添え字1、という考え方になります。
このように、添え字は「先頭からの距離(オフセット)」を表している、というイメージを持つと理解しやすくなります。
添え字は0から始まる
添え字が0から始まる理由
添え字が0から始まる理由は、C言語が配列の場所を計算するときに「先頭からの距離」で考えているからです。
C言語の配列要素arr[i]は、内部的には次のように計算されています(疑似的な説明です)。
- 配列の先頭のアドレスを
baseとする - 要素1つの大きさを
sizeof(要素の型)とする - 添え字
iの要素の位置はbase + i * sizeof(要素の型)
ここでiに0を入れると「base + 0」になり、先頭の要素になります。
つまり、先頭を表すのにちょうど良い数字が0なのです。
擬似コードで説明すると、次のようなイメージになります。
// 実際のCコンパイラはこんなコードを書きませんが、考え方のイメージです
// arr[i] の位置はこんな感じで決まるイメージ
// 先頭アドレス + i * 要素のサイズ
要素のアドレス = 先頭アドレス + (添え字i) * sizeof(要素の型);
「距離0」はまさにスタート地点ですから、添え字0が先頭の要素に対応している、というわけです。
0〜(サイズ-1)が有効な範囲
配列int arr[5]のように宣言した場合、有効な添え字の範囲は0〜4になります。
これは、サイズが5なら、0から数えて5個分の要素があるからです。
一般的には、配列のサイズをNとしたとき、有効な添え字は0〜(N – 1)です。
| 配列の宣言 | 要素数(サイズ) | 有効な添え字範囲 |
|---|---|---|
| int a[3]; | 3 | 0, 1, 2 |
| int b[5]; | 5 | 0, 1, 2, 3, 4 |
| double data[10]; | 10 | 0〜9 |
| char str[100]; | 100 | 0〜99 |
この範囲の中だけが安全にアクセスしてよい「正しい番号」になります。
範囲外アクセスが危険な理由
配列の有効な範囲を超えてアクセスすることを、範囲外アクセス(out-of-bounds access)と呼びます。
C言語では、範囲外アクセスをしてもコンパイラが自動で止めてくれないため、とても危険です。
次のコードを見てください。
#include <stdio.h>
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
// 有効な範囲: 添え字0〜4
printf("arr[4] = %d\n", arr[4]);
// 範囲外アクセスの例(絶対に真似しないこと)
printf("arr[5] = %d\n", arr[5]); // 添え字5は範囲外
return 0;
}
(実行結果の一例: 環境によって変わります)
arr[4] = 5
arr[5] = 32767 ← 意味不明な値になることが多い
このプログラムはコンパイル自体は通りますが、arr[5]は「配列の外」のメモリを読んでしまっています。
範囲外アクセスが危険な理由は次の通りです。
- 予測不能な値が読み出されるため、バグの原因になる
- 他の変数やシステムが使っているメモリを壊してしまう可能性がある
- プログラムが突然終了したり、セキュリティ上の脆弱性につながったりする
C言語では、配列の範囲を自分で守る必要があるので、添え字の範囲を意識してコードを書くことがとても大切です。
配列の要素に番号でアクセスする基本
配列要素へのアクセス書き方
配列の要素にアクセスするときは、配列名の後に[]で添え字を書くというのが基本の書き方です。
- 読み取り:
値 = 配列名[添え字]; - 書き込み:
配列名[添え字] = 値;
具体的な例を見てみましょう。
#include <stdio.h>
int main(void) {
int scores[3]; // テストの点数を3つ保存する配列
// 配列の各要素に値を書き込む
scores[0] = 80; // 1人目
scores[1] = 90; // 2人目
scores[2] = 75; // 3人目
// 配列の各要素を読み取って表示する
printf("1人目の点数: %d\n", scores[0]);
printf("2人目の点数: %d\n", scores[1]);
printf("3人目の点数: %d\n", scores[2]);
return 0;
}
1人目の点数: 80
2人目の点数: 90
3人目の点数: 75
「何番目のデータか」を明確にしたいときに、添え字付きの配列はとても便利です。
読み取りと代入の書き方
配列の要素は、普通の変数と同じように代入や計算に使えます。
違いは「変数名に[]が付いている」だけです。
次の例では、配列の要素を使って合計と平均を計算しています。
#include <stdio.h>
int main(void) {
int scores[3];
// 値の代入(書き込み)
scores[0] = 80;
scores[1] = 90;
scores[2] = 75;
// 読み取りと計算
int sum = 0;
sum = scores[0] + scores[1] + scores[2]; // 要素を足し合わせる
double average = sum / 3.0; // 平均(小数)を求める
// 結果の表示
printf("合計点: %d\n", sum);
printf("平均点: %.2f\n", average);
return 0;
}
合計点: 245
平均点: 81.67
このように、配列の要素は「配列名[添え字]」と書けば1つの変数として扱えることを意識すると理解しやすくなります。
定数添え字と変数添え字の違い
添え字には、数字そのもの(定数)を書くこともできますし、変数を書くこともできます。
- 定数添え字の例:
scores[0]、scores[1] - 変数添え字の例:
scores[i]、scores[index]
定数添え字は、特定の要素だけ扱いたいときに使います。
変数添え字は、for文などで「順番に全部の要素を扱う」ときに使います。
次のコードでは、変数iを添え字として使っています。
#include <stdio.h>
int main(void) {
int scores[3] = {80, 90, 75};
int i;
// 変数iを添え字として使って、全要素を表示する
for (i = 0; i < 3; i++) {
// iは0→1→2と変化していく
printf("%d人目の点数: %d\n", i + 1, scores[i]);
}
return 0;
}
1人目の点数: 80
2人目の点数: 90
3人目の点数: 75
配列を本格的に活用するには、変数を添え字に使うことがほぼ必須ですので、必ず慣れておきましょう。
典型的な間違いと防ぎ方
配列の添え字は、初心者が特につまずきやすい部分です。
ここではよくある間違いとその防ぎ方を説明します。
1つずれてしまう(off-by-one)エラー
最も多いのが、ループの条件が「1つずれている」ことによるエラーです。
次のコードを見てください。
#include <stdio.h>
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
int i;
// 間違った例: i <= 5 にしてしまっている
for (i = 0; i <= 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]); // iが5のときに範囲外アクセス
}
return 0;
}
(実行結果の一例: 環境により異なります)
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
arr[5] = 32767 ← ここが範囲外アクセス
配列の有効な添え字は0〜4なのに、ループの条件でi <= 5としてしまっているため、i = 5のときにarr[5]にアクセスしてしまいます。
防ぎ方として、ループの条件は「<」にすると覚えておくとよいです。
- 正しい形:
for (i = 0; i < 配列のサイズ; i++)
この形を「型にはめて覚える」のがおすすめです。
マイナスの添え字を使ってしまう
もう1つの典型的な間違いは、添え字がマイナスになるケースです。
C言語では、添え字は必ず0以上でなければなりません。
次のようなコードは誤りです。
#include <stdio.h>
int main(void) {
int arr[3] = {10, 20, 30};
int i = -1;
// 絶対にやってはいけない例: 負の添え字
printf("arr[-1] = %d\n", arr[i]); // arr[-1] は未定義動作
return 0;
}
(実行結果は環境によって完全に不定です)
arr[-1] = 123456789 など
負の添え字は配列の前のメモリを読んでしまうため、非常に危険です。
変数を添え字に使うときは、その変数が0以上であることを意識しておきましょう。
防ぎ方のポイント
典型的な間違いを防ぐために、次のような意識を持つとよいです。
- 有効な添え字は「0〜サイズ-1」だと常に意識する
- for文では必ず
i < サイズの形を使う - 添え字に変数を使うときは、0以上であるかを注意する
「配列の外には絶対に触らない」という感覚を早い段階で身につけることが大切です。
for文で配列の全要素にアクセスする
for文と配列アクセスの基本パターン
配列を使う場面では、for文で全ての要素を順番に処理することがとても多いです。
C言語の基本パターンとして、次の形を覚えるとよいです。
int a[N];という配列があるときの基本形は、次のようになります。
int i;
for (i = 0; i < N; i++) {
// a[i] を使った処理
}
このパターンさえ覚えておけば、配列の初期化・表示・計算など、様々な処理を安全に書くことができます。
添え字iを使ったループの書き方
それでは、実際の例を使って、添え字iを使った典型的なループを見てみましょう。
#include <stdio.h>
int main(void) {
int i;
int arr[5];
// 1. 配列を初期化(1〜5を代入)
for (i = 0; i < 5; i++) {
arr[i] = i + 1; // iは0〜4なので、arrには1〜5が入る
}
// 2. 配列の中身を表示
for (i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5
このコードでは、次の3つのポイントが重要です。
- 変数iを0からスタートしている
- 条件は「
i < 5」で、5回だけループする - 配列へのアクセスは
arr[i]で行う
「0から始めて、サイズ未満まで」という形を、体で覚えるつもりで何度も書いてみてください。
配列の長さを使った汎用的な書き方
配列を扱うときには、配列のサイズを定数や変数として別に用意しておくと、後からサイズを変えたいときに便利です。
また、添え字の範囲を間違えにくくなるというメリットもあります。
次のコードでは、SIZEという定数を使っています。
#include <stdio.h>
#define SIZE 5 // 配列のサイズを定数として定義
int main(void) {
int arr[SIZE];
int i;
// 配列の初期化
for (i = 0; i < SIZE; i++) {
arr[i] = (i + 1) * 10; // 10, 20, 30, 40, 50 を代入
}
// 配列の表示
for (i = 0; i < SIZE; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
この書き方の良い点は、配列のサイズを1か所変えるだけで、ループの範囲も自動的に変わることです。
もし#define SIZE 10に変えれば、配列のサイズもループ回数も自動的に10になります。
サイズを増やしたのにループ回数を変え忘れてバグになるといったミスを防ぐことができます。
for文での初期化・表示・計算の例
最後に、for文と配列アクセスを組み合わせた、少し実用的な例を見てみましょう。
次のプログラムでは、5人分の点数を配列に入れて、合計点と平均点を計算しています。
#include <stdio.h>
#define SIZE 5
int main(void) {
int scores[SIZE];
int i;
int sum = 0;
double average;
// 1. 点数を代入(ここではサンプルとして直接代入しています)
for (i = 0; i < SIZE; i++) {
scores[i] = 60 + i * 5; // 60, 65, 70, 75, 80 と代入
}
// 2. 点数を表示
printf("各人の点数:\n");
for (i = 0; i < SIZE; i++) {
printf("%d人目: %d点\n", i + 1, scores[i]);
}
// 3. 合計点を計算
for (i = 0; i < SIZE; i++) {
sum += scores[i]; // sum = sum + scores[i];
}
// 4. 平均点を計算
average = sum / (double)SIZE;
// 5. 合計と平均を表示
printf("合計点: %d点\n", sum);
printf("平均点: %.2f点\n", average);
return 0;
}
各人の点数:
1人目: 60点
2人目: 65点
3人目: 70点
4人目: 75点
5人目: 80点
合計点: 350点
平均点: 70.00点
この例では、以下のような流れになっています。
- for文で配列全体を初期化している
- for文で配列全体を表示している
- for文で配列全体を足し合わせて合計を計算している
どの場面でも、「0からサイズ未満まで、添え字を1ずつ増やしながら配列名[i]にアクセスする」というパターンが使われています。
このパターンをマスターすれば、配列を使った多くの処理を安全かつ簡潔に書けるようになります。
まとめ
この記事では、C言語における配列の要素へのアクセス方法について、添え字の意味や0から始まる理由、そしてfor文との組み合わせ方を解説しました。
添え字は「先頭からの距離」を表す番号であり、有効範囲は0〜(サイズ-1)です。
範囲外アクセスは危険なので、for文では必ずi = 0; i < サイズ; i++という形を身につけておきましょう。
配列と添え字を正しく理解できれば、C言語で扱えるデータの幅が一気に広がります。
