「組み込み」ならではの基礎知識 ――スタートアップ・ルーチンからハードウェアまで
●ポインタを考えると配列が見えてくる
次に,配列とポインタの関係を説明します.前の例を用いれば,arrayは配列の先頭アドレスを表すので,
array = 0x1020
です.
また各配列要素のアドレスは,&演算子(指定されたデータのアドレスを計算する)を用いて,以下のように表せます.
&array[0] = 0x1020
&array[1] = 0x1024
&array[2] = 0x1028
&array[3] = 0x102c
&array[4] = 0x1030
&array[5] = 0x1034
これらのアドレスの計算は,先ほどの式(3-1)の計算と同じです.これらのアドレスの情報を格納するために使われるものがポインタです.ポインタはアドレス情報だけでなく(それだけなら整数データとして扱えば十分),そのアドレスにデータを書き込む,あるいはそのアドレスに格納されているデータを読み出すためにも使います.そのためには型情報が必要なので,ポインタは型情報もあわせ持ちます.つまり,
int *p = array;
と記述すると,pは配列arrayの先頭を指し示すポインタで,そのポインタの示すアドレスの中身はint型であることがわかります.
組み込みソフトウェアから見ると,配列とはメモリ上に貼り付いたある領域であって,特別なものではありません.上で述べたようなアドレス計算はすべてCコンパイラが行っているので,配列とはCコンパイラの作り出した虚像であるとも言えます.つまり配列の実体は,配列と同じ型情報を持った,配列の先頭のアドレスを指し示すポインタであると考えられます.
ところで,(int *)0x1020という表現の意味がわかりますか? 0x1020は整数ですが,これをアドレスと考えると,(int *)のキャストで型情報が付いたポインタ(つまり,アドレス0x1020でその内容はintであるポインタ)である,と解釈できます.そうです.
int *p = (int *)0x1020; (3-2)
は,
int *p = array;
と実質的に同じことを表現しています.普通のC言語のプログラムでは式(3-2)のような表現を使いませんが,組み込みプログラムでは周辺I/Oをアクセスするためのレジスタ操作のために日常的に使うので,ぜひ覚えておいてください.
では,array[-1]を考えます.式(3-1)と同様に考えると,次のようになります.
array[-1]のアドレス = 配列arrayの先頭アドレス +(-1)番目の要素のオフセット
= 0x1020-1×sizeof(int)
= 0x1020-0x14
= 0x101c
Cコンパイラは機械的にアドレス計算を行います.つまり,array[-1]は0x101c番地のintデータを指していることになります(図3-1に基づくと,array[-1]の値は「256」となる).プログラムの動作に対して「あれっ?」と思ったときは,配列のインデックスが負になっているのかもしれません.