第八回 12 月 01 日

4 ポインタ

4.3 参照呼び出し

変数を引数として関数を呼び出す場合, 変数の値が関数の仮引数にコピー (ヒープ領域 heap area と呼ばれるメモリ領域に確保される) されて関数が呼び出される。 呼び出された側の関数は, (実引数ではなく) このヒープ領域にコピーされた変数の値のコピーを利用して計算する。 従って,呼び出された側の関数でいくら変数の値を変更しても, その変更結果が呼び出した側の関数の変数に反映されない。 このような関数の引数の値の呼び出し方を値呼び出し call by value と呼ぶ。 pass-by-value.c

C では,呼び出された側の関数の中で, 呼び出した側の関数で用いられている変数を変更する方法は用意されていない。 変数の値を関数に渡し, 呼び出された側でその変数の値を変更するためには, ポインタ型の変数を用いて,変数のアドレスを渡すしかない。 このようなアドレス変数を介した変数の受け渡しを参照渡し pass by reference と呼ぶ。 また,参照渡しによる関数の呼び出しを参照呼び出し call by reference と呼ぶ。 swap.c

演習 4.4 swap.c の変数 a と b とをキーボードから入力するように修正せよ。
演習 4.5 演習 4.4 を double 型のデータ用に修正せよ。

変数の値を関数の内部で操作したいときには,

ということをしなければならない。
既に学んだとおり,return 文を使えば, 呼び出された関数から一つだけ値を返すことができる。 呼び出し側に複数の変数を変更したい場合や, 大量のデータを値呼び出しで呼び出す場合のオーバーヘッド (ヒープ領域にコピーが作られることによる処理系への負荷)をなくすには, 関数にポインタを渡す機能がどうしても必要となる。

一般的な C の関数の実装においては, 変数の値を操作したい場合には,参照渡し pass by reference すなわちアドレスを渡して, 呼び出された側ではポインタを使って変数を操作する。 その際,関数の実行結果が成功したか失敗したかを関数の戻り値 return value とすることが行われ,一般的なコーディングスタイルである。

4.5 ポインタの算術演算

ポインタは

を持っている。ptr をある変数を指すポインタであるとすると, ptr++ で,その型の大きさ(sizeof()で知ることができる) に合致したアドレスが取得できる。 たとえば,ptr が int 型変数のポインタであれば、 p++ は 4 バイト先のアドレスを指す、 ptr が double 型の変数のポインタであれば,8 バイト先のアドレスを表す。 例えば,次のサンプルコードを参照せよ intdbl_address.c

ポインタに作用する演算子としては +, -, ++, --, の 4 つがある。 ポインタはアドレスという正の整数の値を持つので整数に許される加減算が可能である。

ポインタ型の変数に許される演算子
演算子ポインタの例
+p+1p が指している要素の次のアドレスを与える
-p-1p が指している要素の前のアドレスを与える
p-qp と q との間の要素数を数える
++p++p が指している次の要素のアドレスを与える
--p--p が指している前の要素のアドレスを与える

ポインタ演算子 *, & は算術演算子より 優先順位が高く *, &, ++, -- は右から左に計算される。 例えば,*p++ は ++ が p の後ろについてるため, *(p++) と解釈される。 すなわち p のアドレスが 1 個インクリメントされて,その参照する変数の値を指す。

(重要)

*p(ポインタの指す変数の値そのもの)をインクリメントしたければ, (*p)++ としなければならない。

また,*++p は *(++p) と解釈される。すなわち, ポインタのアドレスを 1 個インクリメントした後のアドレスにある値を指す。 pointer-arithmetic.c

4.6 配列とポインタ

a[] という配列変数があったとする。このとき, *(a+1) は a[1] と正確に同じ意味になる。 従って, 配列の n+1 番目の要素を参照したければ *(a+n) となる (このとき,n は int 型の変数でなければならない)。 array_pointer.c

整数型の配列 a[3] と整数型へのポインタ aPtr が宣言されているとする。 添字なしの配列名はその配列の先頭要素へのポインタである。
なので,以下の文のように配列 a の先頭アドレスを aPtr にセットすることができる。

    aPtr = a;

これは配列の先頭要素のアドレスを使った以下の文と同義である。

    aPtr = &a[0];

このとき,配列要素 a[2] は次のポインタの式でも参照可能である。

    *(aPtr + 2)

この式中の 2 は,ポインタ aPtr からどのくらい離れているかを表す数字なり オフセットと呼ばれたりする。 オフセットの数字と配列の添字とは等しい。 * 演算子の方が + 演算子よりも優先順位が高いので「aPtr+3」を括弧で囲む必要がある。 括弧がないと

    *aPtr + 2

は,*aPtr に 2 を足すという意味に解釈されてしまう。 すなわち aPtr は配列の先頭を指しているので a[0] に 2 を加えた数が返される。 ポインタを使って配列の要素にアクセスできるように,

    &a[2]

も以下のようにポインタを使って,

    aPtr + 2

のように表現できる。

配列名自体もポインタとして扱えるので,ポインタ演算を使うことができる。 たとえば,次の式

    *(aPtr + 1)

は,先に述べたように a[1] と同義である。サンプルコードを以下に示す。
pointer_array.c

逆に,ポインタを配列として記述することも可能である。例えば

    aPtr[1]

は,配列要素 a[1] を参照する。 このような書き方をポインタの添字表記法と呼ぶ。 配列表記とポインタ表記を使った文字列のコピーのサンプルコードを以下に示す。 pointer_copy.c

演習 4.6

array_pointer.c の printf() 文の a[0], a[1], a[2] を p[0], p[1], p[2] にして実行せよ。

配列とポインタの関係
ポインタ p 配列 a
配列の先頭要素のアドレスp または &p[0]a または &a[0]
配列の先頭要素*p または p[0]*a または a[0]

次の例は,a[3] という要素の数が 3 である配列 a[3] に対して,intPtr というポインタ変数 に a (すなわち a の先頭の要素のアドレス &a[0])を代入し,その値を調べて,それぞれのアドレスと値を印刷している。 array_pionter2.c

また,次のサンプルプログラムも配列 a[3] の中の 1 番目の要素 a[0] のアドレスと要素の値を印刷している。 array-pionter3.c

演習 4.7 array_pointer3.c を改造し,a[1] のアドレスと値とを出力するようにせよ。

解答例


戻る

Shin-ichi ASAKAWA
asakawa@ieee.org