「組み込み」ならではの基礎知識 ――スタートアップ・ルーチンからハードウェアまで
3 array[-1]はなぜ動くのか(配列の実体)
プログラムを作るとき,配列ほど使いやすいものはありません.そのため,いつのまにか「配列」という抽象的な概念が頭の中にでき上がって,何か特別なもののように思っていませんか? ここでは,C言語における配列の実体に迫ります.
●メモリ空間のイメージを思い浮かべよう
配列を使うときに注意するべきことは,例えば,
int array[10];
と宣言した場合,array[0],array[1],...,array[9]というように「インデックスは0から9を使うこと」です.インデックスをこの範囲で使うというのが,礼儀正しいプログラムの作りかたです.しかし,範囲を超えたインデックスを使ってもプログラムは動作します(動作として正しいかどうかは別として).極端な話,array[-1]でも動きます.なぜなのか説明できますか?配列という概念だけでこれを説明することはできません.配列の実体を考えます.
まず,メモリ空間のイメージを頭に思い浮かべましょう.メモリ空間とは,プログラムのコードやデータを配置するための(論理的な)場所で,実際にはマイコンの内蔵メモリやマイコン外部にあるRAMやROMに存在します注3-1.MMU(memory management unit)を搭載するマイコンもありますが,ここでは説明を簡単にするためにMMUはないものとします.メモリ空間にはアドレスが割り振られ,それによってユニーク(一意)にどこのメモリかを指定できます.メモリ空間のアドレスは,例えば32ビットを使って表現すれば,0x00000000番地から0xffffffff番地まで割り振ることができますが,実際に使用する際にはこれらのすべてのアドレスが使えるわけではありません.アドレスに対応するメモリが実装されている部分のみ使えることになります.例えば図3-1では,0x00000000番地から0x0000ffff番地までと0xffff0000番地から0xffffffff番地までが使用できることを示しています.
さて,配列arrayはこのメモリ空間の中でどのように存在しているのでしょうか?図3-1では,配列arrayは0x1020番地から0x1047番地までに配置されています.これによりarray[0]は0x1020番地,array[1]は0x1024番地,array[2]は0x1028番地,...となります.これらのアドレスは,配列の各要素のそれぞれに対して何番地と割り振るわけではなく,配列の先頭アドレスとオフセットから計算されます.つまり,array[5]の場合,
array[5]のアドレス = 配列arrayの先頭アドレス + 5番目の要素のオフセット
= 0x1020 + 5 × sizeof(int)
= 0x1020 + 0x14 = 0x1034 (3-1)
となります.つまり,配列の先頭アドレスさえ決まれば,インデックスと配列の型からわかるデータ長(この例ではsizeof(int))から配列の各要素のアドレスがわかることになります.つまり,配列を利用するにはメモリ空間上の連続した場所があればよいわけです.
注3-1;メモリ空間については,「8.メモリとポート」も参照していただきたい.