01: プログラミング言語とは何か 02: 0から99まで印字するプログラムの解説 03: Pythonで書くと... 04: 変数と関数 05: 条件分岐とループ 06: プリプロセス 07: ポインタと配列1 08: ポインタと配列2 09: ポインタと配列3 0A: 文字列 0B: 多次元配列 0C: ファイル入出力 0D: 構造体 0E: コマンドライン引数 0F: 数値を読み込んで総和と平均を表示するプログラム 10: 数値を読み込んで総和と平均を表示するPythonプログラム 11:【番外編】文章中の単語を出現頻度順に表示するプログラム 12:【番外編】コマンドライン引数を処理するイディオム 13:【番外編】C言語でクロージャを実装する
ポインタと配列について,別の角度から解説する.
メモリの割り当ては,静的,自動的があることを述べた.calloc関数は第三の方式であって,これを狭い意味での動的と呼ぶ(自動的は広い意味の動的に含まれる).動的な変数のうち,自動的な変数のために確保されたメモリは自動的に破棄される.一方,狭い意味での動的に確保されたメモリは自動的には破棄されない.例えば,
int *func(void) {
int *a;
a = calloc(100, sizeof(int));
return a;
}
として関数funcから取り出したintポインタは,関数funcの外でも「生きて」いる.callocされたメモリを捨てるには,プログラマが明示的にfree関数を呼ばなければならない.ところが,まだ必要なのに捨ててしまうという事故が絶えない.そこで,小さなプログラムでは,callocしたメモリはいつも捨てずに置いておくことを勧める.(C++を除く近代的なプログラミング言語では,メモリは自動管理されているからこのような事故は起こらない.)
callocしたメモリは次のように配列としてアクセスできる.
int *a = calloc(100, sizeof(int));
a[2] = 20; /* aの第3要素(第2要素ではない)に20を代入する */
同じことが,静的変数,自動変数でも出来る.
int b[100]; /* 初期化は省略 */
b[2] = 20;
C言語ではいつでもx[y]と *(x + y) は同じ意味である.ポインタaに対してa[i]は *(a + i) であり,これは配列のi番目の要素を指す.ということは,静的変数,自動変数バージョンのb[i]も *(b + i) と同じという意味である.つまり
int b[100];
int *pointer_version_of_b = b;
とするとpointer_version_of_bはbと同じように使える.pointer_version_of_b[2] = 20 は b[2] = 20 と同じ意味である.なお,pointer_version_of_bの定義は
int *pointer_version_of_b = &b[0];
とする場合もある.こちらのほうがどちらかと言えば通な雰囲気がする(理由は後述).
配列版とポインタ版の唯一の違いは,その変数へ代入が出来るかどうかである.
int *a = calloc(100, sizeof(int));
int b[100];
a += 1; /* OK */
b += 1; /* エラー */
配列に関する注意を列挙する.
- 添え字の範囲チェックは行われない.
- 配列の大きさは変えられない.
- 配列の大きさは配列とは別途管理しなければならない.
- 配列に対して一括操作(例えば全体の2倍するなど)の仕組みはない.
- 多次元配列,連想配列は陽にサポートされない.
Cプログラマは,それでも配列とポインタを使わざるを得ない.
配列,ポインタを使ったプログラムの例を挙げる.次のコードは100個の配列の中身を順に偶数で埋めていく.(Pythonで言うところの a = [2 * x for x in range(0, 50)] である.)
int a[100];
int i;
for (i = 0; i < 100; i += 1) {
a[i] = i * 2;
}
興味深いことにa[100]へのアクセスは無意味であるが(a[0]からa[99]までだけが意味を持つ),&a[100]すなわち a + 100 は意味のある数値であることが保証されている.この性質をC++プログラマは最大限に使う.もしCプログラムの中に次のようなコードを見つけたら,優れたC++プログラマの仕業と見てよかろう.
int a[100];
int *b = &a[0], *e = &a[100];
while (b != e) {
*b = ...;
++b;
}