ポインタ型

ポインタとは

ポインタとは何でしょうか? ポインタ(pointer)を直訳すると『指し示す人や物』になります。 次のような指し示すイメージが浮かびます。


 この場合のがポインタです。

そのほか、レーザーポインタ、マウスポインタのポインタなどの用語もそうで、 何かを『指し示す』ために使われます。

さて、C言語のプログラミングにおいても同じように考えると、 どうでしょうか?それはデータを『指し示す』ために使われるものです
コンピュータにおいてデータは、メモリに記憶されます。その記憶内容を指し示すデータはメモリのアドレス(位置)になります。
つまり、データを『指し示すアドレスデータ』がポインタとなります
一般にポインタデータのサイズは、unsigned int型と同じサイズです。
つまり、32ビットCPUでは0番地から4294967295番地まで扱えます。

これまで、ポインタを扱うデータをいくつか紹介しました。その代表的なものは
 配列の名前と、 scanfの引数で指定した & 変数 の表現です。例を示します。

実際printfで、符号なし10進表示指定の%uを使うと、次のようにメモリに割り当てられた配列の先頭アドレスや、 変数のアドレスを確認できます。
printf ("caの先頭アドレスは %u 番地です\n", ca );
printf ("ddが割り当てられるメモリの位置は %u 番地です\n", & dd );


そして [ ] を使うと、caのポインタから希望の数だけ進んだデータ領域を表現できるわけです。 例えば上記の宣言で、ca[3]はcaが指し示す位置から3個進んだ位置のデータを表現していることになります。 この位置のアドレスデータがポインタです。 つまりC言語において、
[]は、ポインタのデータを左に書いて扱う演算子になっているのです。

  一般に次のような使い方をしませんが、配列でなくても次のような操作を書けます。
  ( & dd )[3] = 12.3;  これは、ddの位置を基準として、3進ませたdouble型記憶領域を12.3に変更しています。 実行において、このアクセス位置が使える記憶空間である時、予想していなかった別の変数の記憶領域を変更してしまって発見しにくい問題を抱えた作品ができてしまいます。 運よく、このアクセス位置が使える記憶空間でない時は、実行時エラーで止まります。 scanfの%の指定を間違えて実行時エラーで止まるのは、このパターンです。

ポインタの整数加算とポインタ演算子

ポインタ(データを指し示すアドレス)は、整数の加減算ができます。
これは住所で『16番地の家』という表現に対して、16番地に対して3つたせば19番地を 表現できるということに似ています。しかしそこに家(記憶域)がないのにアクセスすれば実効エラーです。
C言語においてポインタが指し示す記憶領域が、 プログラマが予想している範囲であるか確かめる機能がありません。 C言語の作品が、他の高級言語で作る作品と比べて高速に動作できるのは、そのチェックを含んでいないためです。 チェックすべきところは、チェックプログラムを作るか、 またはアクセスしてはならない範囲を,アクセスしないようにプログラムしなければなりません。 そうして実行エラーにならないように作るのは,プログラマの責任ということです。

さて、C言語ではポインタを扱う演算子が[]以外にもう一つあります。
それが * のポインタ演算子です。 * 演算子は、 掛け算にも使うので、一人2役の記号というわけです。
[]演算子がポインタデータをに書きますが、* 演算子はポインタデータをに書きます。
あとは演算子としての優先順位と結合規則が異なるだけで、その他の機能は同じです。 (優先順位と結合規則の表を参照ください。
どちらも四則演算子より高い優先順位ですが、* 演算子は右から左に結合するので、 ポインタ(データを指し示すアドレス)に対する整数の加減算を伴う場合に()を必要とする場合が多くなります。

例えば、 int a[10]; で10個のint型記憶域を作った場合、

  実行文で、 aのポインタ位置から3進んだ位置の記憶内容を 1234に変更する処理は、
*(a + 3) = 1234;  と書きます。   つまり、*演算子の右に (a + 3)番地というポインタを表現しているわけです。これは、 a[ 3 ] = 1234;  の処理とまったく同じです。[]演算子はポインタのaを左に書きます。

  実行文で、 aのポインタが指し示す先頭要素が-1と等しくないかの判定式は、
*(a + 0) != -1  と書きます。 ですが、 0の加算は意味がないので、 カッコを付けないで次のようも書けます。 *a != -1;  そして[]演算子を使えば、a[0] != -1;  という表現になります。

  実行文で、 aのポインタ位置から3進んだ位置の記憶内容をscanfで設定する場合は、
scanf( "%d", & a[3] ); と書けますが、 
a + 3 の位置を変更する処理なので、 scanf( "%d", a + 3 ); と書くこともできます。
使うことはないでしょうが、scanf( "%d", & *(a + 3) ); と書いても同じになります。

(なお、単に数値を表現するデータ対して* 演算子を使うと、これまでの通りで掛け算になります。)

具体例 ( []の配列演算子と、*のポインタ演算子の比較 )

3個並べた入力データを、配列に記憶しながら、合計値を変数totalに求めるプログラムを紹介します。

10
5
15
total = 30
#include <stdio.h>
#define NUMB 3	/* 入力個数 */
main()
{
	int ai[NUMB];		/* 入力用配列 */
	int total = 0;		/* 合計値 */
	int  i = 0;			/* 数える変数 */

	while(i < NUMB ) {
		scanf("%d" , & ai[ i ] );
		total += ai[ i ]);
		i++;
	}
	printf("total = %d\n" , total);
}	

上記を配列演算子を使わず、ポインタ演算子に変更すると次のようになります。

#include <stdio.h>
#define NUMB 3	/* 入力個数 */
main()
{
	int ai[NUMB];		/* 入力用配列 */
	int total = 0;		/* 合計値 */
	int i = 0;			/* 数える変数 */

	while( i < NUMB ) {
		scanf("%d" , ai + i );
		total += *(ai + i);
		i++;
	}
	printf("total = %d\n" , total);
}	

進む順番で説明します。⇒