配列とポインタの関係

配列名はほとんどの場面で先頭の番地になる

前回、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]) で要素数が求まるのは、この例外のおかげです。ただしこれは配列が見えている場所でしか使えません。関数の中の引数はもうポインタなので、同じ式を書いても要素数にはなりません。よくある取り違えです。

では、添字版と間接参照版で同じ結果になることを、実際に確かめてみましょう。

要件

  1. int sum_index(const int *scores, int n) は scores[i] を使って合計する
  2. int sum_deref(const int *scores, int n) は *(scores + i) を使って合計する
  3. 2 つの合計を表示し、そのあと一致したかどうかを表示する
  4. 3 人目と 1 人目の番地の差を要素数で表示する
  5. 出力は「添字版の合計は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要素です"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.c
学習モード

メモ

配列とポインタの関係

⌘S で保存