🗃️ 配列
🎯 境界外アクセスが未定義動作であることを理解し、危険性と防ぎ方を説明できる
このトピックは、C言語を学ぶうえで最も重要な安全教育です。文法よりも先に、体に刻んでください。
int a[3] と宣言したのに a[3] や a[5] に触れる。これを境界外アクセスと呼びます。そしてCは、それを黙って通します。
#include <stdio.h>
int main(void)
{
int a[3] = {10, 20, 30};
int x = 99;
a[3] = 777; // 配列の外!
printf("x = %d\n", x); // x = 777 になることがある
return 0;
}x は一度も書き換えていないのに、値が変わることがあります。配列のすぐ隣に置かれていた x の場所を、a[3] が踏んだからです。下のビジュアライザで、その瞬間を見てください。
🧪 ここはメモリの中身を1ステップずつ追う「メモリビジュアライザ」の場面です(アプリ本体で動きます)。
なぜCは止めてくれないのか
多くの言語は、範囲外の添字を検出してエラーで止めてくれます。Cがそうしないのは、チェックの費用を払わないという設計思想だからです。要素にアクセスするたびに「範囲内か?」と確認すれば、その分だけ必ず遅くなります。Cは「プログラマが正しく書く」ことを前提に、その確認を省いて速度を取りました。OSや組込み機器がCで書かれてきた理由でもあり、同時に、事故がプログラマの責任になる理由でもあります。
いちばん怖いのは「すぐ落ちない」こと
境界外アクセスは未定義動作です。何が起きるかは決まっていません。
「動いたから正しい」がまったく通用しないのが、この事故の恐ろしさです。数か月後、まったく別の場所を修正した日に突然表面化します。しかも壊れるのは「はみ出した側」ではなく「踏まれた側」なので、調査は無関係な変数から始まります。原因にたどり着くまでの遠回りが、この事故の本当のコストです。
セキュリティの入口でもある
配列の外へ書き込める状態は、攻撃者から見れば「プログラムの記憶を書き換えられる穴」です。歴史上の深刻な脆弱性の多くが、このバッファオーバーフローに起因しています。第6部で正面から扱いますが、いまの段階では「配列の外に書くのは、鍵をかけ忘れた玄関と同じ」と覚えておいてください。
防ぎ方
i < n。<= を書いたら一度立ち止まる#define などで1か所にまとめ、配列とループで同じ名前を使うif (i >= 0 && i < n) で確認するとくに4番目が実戦では効きます。添字が変数で決まるときは、触る前に範囲を確かめる。それだけで多くの事故は防げます。
#include <stdio.h>
#define N 3
int main(void)
{
int a[N] = {10, 20, 30};
int i = 3; // 外から来た値のつもり
if (i >= 0 && i < N) { // 触る前に確認する
printf("%d\n", a[i]);
} else {
printf("範囲外です\n");
}
return 0;
}まとめ: 境界外アクセスは未定義動作。落ちないことすらある。だからこそ「範囲は 0 〜 n−1」を、書くたびに確認する習慣が最大の防御になります。
配列の要素数を超えた添字にアクセスしてしまう重大な事故のこと。コンパイルは通ってしまうため、実行時に予期せぬ動作を引き起こす。他の変数の領域を書き換えてしまい、原因の分かりにくいバグにつながりやすい。
もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。