プログラミングで「配列外参照」という言葉を見かけることがあります。これは、配列に存在しない位置を指定して値を読み取ったり、書き込んだりしようとすることを意味します。
特にC、C++、Java、C#、JavaScriptなど配列を扱う言語では、添字やインデックスの指定を間違えると配列外参照が起こることがあります。言語によってはすぐエラーになる一方、CやC++のように危険な動作へつながる場合もあります。
配列外参照を理解するには、まず「配列の番号はどこからどこまで有効なのか」を理解することが重要です。ここでは初心者向けに、具体例を使いながら原因と防ぎ方を解説します。
配列外参照とは「存在しない要素番号を指定すること」
配列は、複数の値を順番に保存するためのデータ構造です。例えば5個の整数を保存する配列がある場合、一般的なプログラミング言語では要素番号は0から始まります。
つまり要素数が5なら、有効なインデックスは0、1、2、3、4です。5番目まで存在するからといって、インデックス5が使えるわけではありません。
例えば次のような配列を考えます。int a[5]; この場合、a[0]からa[4]までは有効ですが、a[5]を参照すると配列の範囲を超えています。これが配列外参照です。
なぜ「要素数5なのに5番が使えない」のか
初心者が最も混乱しやすいのが、配列の番号が0から始まる点です。5個の要素を持つ配列では、最初の要素が0番、2個目が1番、最後の5個目が4番になります。
例えば配列{10,20,30,40,50}がある場合、a[0]は10、a[1]は20、a[4]は50です。
一方でa[5]を指定すると、6個目の要素を要求していることになります。しかし実際には5個しかないため、配列外参照になります。
配列外参照が起こる典型的なコード例
よくあるのが、for文の終了条件を間違えるケースです。
int a[5] = {1,2,3,4,5}; for (int i = 0; i <= 5; i++) { printf("%d\n", a[i]); }
このコードでは、iが0から5まで動きます。そのため最後にa[5]を参照してしまい、配列外参照になります。
正しくは、i < 5とします。するとiは0、1、2、3、4までになり、配列の範囲内だけを参照できます。
「<」と「<=」の間違いが配列外参照を起こしやすい
配列外参照の原因として非常に多いのが、ループ条件で<と<=を取り違えることです。
配列の要素数がnなら、多くの場合インデックスは0からn-1までです。そのためループはi < nと書くのが基本です。
例えば要素数10なら、有効なのは0~9です。i <= 10とすると10まで実行されるため、最後に存在しない11個目の要素を参照してしまいます。
配列外参照すると必ずエラーになるとは限らない
ここで重要なのは、配列外参照をしたときの挙動がプログラミング言語によって異なることです。
JavaやC#などでは、範囲外の配列要素を参照すると例外が発生し、プログラムがその場で停止することがあります。この場合は原因を発見しやすいといえます。
一方、CやC++では配列の範囲外を参照しても、その場ですぐエラーにならない場合があります。たまたま別のメモリを読んだり書き換えたりし、不正な値、クラッシュ、データ破壊などにつながる可能性があります。
CやC++の配列外参照が特に危険な理由
CやC++では、組み込み配列に対する範囲チェックが自動では行われないのが一般的です。そのため、a[5]のような間違った参照を書いてもコンパイルできることがあります。
例えば本来の配列の直後に別の変数が置かれていた場合、配列外への書き込みによってその変数の値を壊してしまう可能性があります。さらに悪い場合には、プログラムの制御に関係するメモリへ影響し、深刻な不具合につながります。
このように、配列外参照は単に「変な値が出る」というだけではなく、セキュリティ上の脆弱性につながることもあります。
Javaなどではどのようなエラーになるのか
Javaでは配列の範囲外を参照すると、一般にArrayIndexOutOfBoundsExceptionという例外が発生します。
例えばint[] a = new int[5]; System.out.println(a[5]);のように書くと、有効範囲は0~4なので例外になります。
このような言語では、エラーメッセージに不正なインデックス値が表示されることがあります。スタックトレースやエラー行を確認し、どの配列でどの番号を参照しているか調べると原因を特定しやすくなります。
負のインデックスも配列外参照になることがある
配列外参照というと「大きすぎる番号」だけを想像しがちですが、負の値にも注意が必要です。
C、C++、Java、C#などでは、通常a[-1]のような参照は有効な配列要素ではありません。例えばループや計算式によってインデックスがマイナスになってしまうと、範囲外参照になります。
具体例として、a[i-1]を参照するコードでiが0になると、a[-1]になります。前の要素を参照する処理では、i > 0かどうか確認する必要があります。
ユーザー入力をそのままインデックスに使う場合も注意する
配列のインデックスをユーザー入力から決める場合は、入力された値が範囲内か確認する必要があります。
例えば10個のデータを持つ配列に対してユーザーが10や100を入力した場合、その値をそのままインデックスに使うと配列外参照になる可能性があります。
そのため、0 <= index && index < 配列の要素数という条件を満たしているか確認してからアクセスするのが基本です。
配列外参照を防ぐ基本的な方法
最も基本的な対策は、配列の要素数とインデックスの範囲を常に意識することです。要素数がnなら、0始まりの配列では通常0からn-1までが有効です。
ループでは固定値を直接書くより、配列の要素数を取得できる仕組みを使うと安全です。例えばC++のstd::vectorならsize()、Javaの配列ならlengthを利用できます。
さらに、インデックスそのものが不要なら、要素を直接順番に取り出すfor-each形式のループを使う方法もあります。添字を書かなければ、それだけ配列外参照のミスを減らせます。
配列外参照とオフバイワンエラーの関係
配列外参照と深く関係する言葉に「オフバイワンエラー(off-by-one error)」があります。これは、回数や境界を1つだけ間違える典型的なプログラミングミスです。
例えば「0~4まで繰り返したいのに0~5まで実行してしまう」というミスが該当します。配列外参照の多くは、この1つ多い・1つ少ないという境界条件の間違いから発生します。
デバッグするときは、ループの開始値、終了条件、配列の要素数、実際に使われる最大インデックスを紙に書き出して確認すると原因を見つけやすくなります。
多次元配列でも配列外参照は起こる
2次元配列や多次元配列では、それぞれの方向について範囲を確認する必要があります。
例えば3行4列の配列なら、0始まりの場合、行番号は0~2、列番号は0~3です。a[3][0]は行方向で範囲外、a[0][4]は列方向で範囲外になります。
画像処理、ゲームのマップ、行列計算などでは2次元配列を頻繁に使うため、「横幅」と「高さ」のどちらをインデックスに使っているか取り違えないことも重要です。
配列外参照が疑われるときの確認ポイント
プログラムが突然落ちる、意味不明な値が出る、配列アクセス周辺で例外が発生するといった場合は、インデックスの値を確認します。
- 配列の要素数はいくつか
- 最小インデックスはいくつか
- 最大インデックスはいくつか
- ループ条件が
<ではなく<=になっていないか - インデックスが負になっていないか
- ユーザー入力や計算結果がそのまま使われていないか
- 2次元配列の行・列を取り違えていないか
デバッガやログ出力を使って、配列へアクセスする直前のインデックス値を表示するのも効果的です。
まとめ:配列外参照は「配列に存在しない番号へのアクセス」
配列外参照とは、配列に存在しないインデックスを指定して要素を読み書きしようとすることです。要素数5の0始まり配列なら、使える番号は0~4であり、5や-1などは範囲外になります。
代表的な原因は、for文の終了条件で<と<=を間違えること、配列サイズを勘違いすること、計算結果やユーザー入力によってインデックスが範囲外になることです。
JavaやC#では例外として検出されやすい一方、CやC++ではすぐ異常が見えず、メモリ破壊など深刻な問題につながる場合があります。そのため「0以上か」「要素数未満か」を確認し、配列サイズを基準にループを書く習慣が重要です。


コメント