配列とポインタの関係
配列名はほとんどの場面で先頭の番地になる
前回、int *p = scores; と書きました。よく見ると不思議な行です。scores は配列なのに、ポインタに & も付けずに代入できています。
C には「配列名は、式の中に置かれると先頭要素へのポインタに読み替えられる」という決まりがあります。scores と書いた時点で &scores[0] に化ける、ということです。だから int *p = scores; は通りますし、scores + 1 も書けます。
配列とポインタは別物です。配列は要素が並んだ実体そのもの、ポインタは番地を入れる箱でしかありません。ただ、式の中では配列名が先頭のポインタに変わるので、両者が同じもののように見えます。この読み替えを知っているかどうかで、C の見え方が変わります。
a[i] は *(a + i) の別の書き方
添字も、この読み替えの上に乗っています。C の規格は a[i] を *(a + i) の省略形と定めています。
scores[2] /* これは */
*(scores + 2) /* これと完全に同じ意味 */同じ意味なので、ポインタにも添字が使えます。p[2] は *(p + 2) です。関数の引数を int scores[] と書いても int *scores と書いても、コンパイラにとっては同じ意味になります。
a[i] が *(a + i) なら、足し算の順番を入れ替えた *(i + a)、つまり i[a] も文法上は通ります。実際 2[scores] はコンパイルできてしまいます。書くべきではありませんが、この読み替えが本当に足し算であることの証拠になります。
関数に渡すと長さが消える
読み替えの一番の実害がここです。配列を関数に渡すと、渡っているのは先頭の番地だけです。要素が何個あるかという情報は付いてきません。
void show(int scores[]) {
/* ここでは何個あるか分からない */
}だから C では、配列を渡すときに必ず個数も一緒に渡します。入門で void show(const int scores[], int n) の形を繰り返し書いてきたのは、言語がそう作られているからです。
sizeof だけは読み替えが起きない
読み替えの例外が sizeof です。
int scores[4];
int *p = scores;
sizeof(scores) /* 配列全体のバイト数。int 4 個分で 16 */
sizeof(p) /* ポインタ 1 個分の大きさ。配列とは無関係 */sizeof(scores) / sizeof(scores[0]) で要素数が求まるのは、この例外のおかげです。ただしこれは配列が見えている場所でしか使えません。関数の中の引数はもうポインタなので、同じ式を書いても要素数にはなりません。よくある取り違えです。
では、添字版と間接参照版で同じ結果になることを、実際に確かめてみましょう。
要件
- int sum_index(const int *scores, int n) は scores[i] を使って合計する
- int sum_deref(const int *scores, int n) は *(scores + i) を使って合計する
- 2 つの合計を表示し、そのあと一致したかどうかを表示する
- 3 人目と 1 人目の番地の差を要素数で表示する
- 出力は「添字版の合計は400点です」「間接参照版の合計は400点です」「2つの結果は同じです」「3人目と1人目の差は2要素です」の形にする
入出力例
main("80 90 75 70 85") → "添字版の合計は400点です
間接参照版の合計は400点です
2つの結果は同じです
3人目と1人目の差は2要素です"
main("60", "60", "60", "60", "60") → "添字版の合計は300点です
間接参照版の合計は300点です
2つの結果は同じです
3人目と1人目の差は2要素です"
main("100 0 45 63 92") → "添字版の合計は300点です
間接参照版の合計は300点です
2つの結果は同じです
3人目と1人目の差は2要素です"