Page 7 ポインタ

ポインタはC言語を学習していく上でもっともつまづきやすい部分です。

しかし、C言語と言えばポインタであり、ポインタの理解なくしてC言語は語れません。

ゆっくりやっていきましょう。

変数とポインタ

今まで変数に値を保存することをやってきました。例えば、

#include <stdio.h>

int main(){
	int a;
	a = 1;
	return 0;
}

で、aに1という値が入りました。

#include <stdio.h>

int main(){
	int a;
	a = 1;
	printf("%d\n",a);
	return 0;
}

これでaの中身が1であることが確認できます。

ところで、この値は、コンピュータのメモリのどこかに格納されています。メモリはコンピュータの性能(スペック)のところにかかれているあれです(1GBとか2GBとか)。

スペック

変数の格納されているアドレスは次のようにして調べられます。

#include <stdio.h>

int main(){
	int a;
	a = 1;
	printf("%d\n",&qmp;a);
	return 0;
}

変数の前に&をつけるとアドレスになります。このプログラムを実行すると、ある数値が出力されます。(値は予測できません)

普通に変数を使用している場合は変数を格納しているアドレスなど特に意識しなくてもいいのですが、ポインタを使用するときはそれをあえて意識します。アドレスの値を覚えておく必要はありません。「どこかのアドレスに数字を記憶させたんだ」ということを意識すればそれで充分です。

とりあえず、ここでは「&をつけるとアドレスになる」ということだけ覚えておいて、次にいきましょう。

ポインタを使用する

ポインタを使用する場合は変数名の前に*をつけます。これで、変数をポインタとして定義できます。

int *a;

こんな感じでポインタの宣言ができます。

#include <stdio.h>

int main(){
	int *a;

	/* 処理が入ります */

	return 0;
}

人によっては「int* a」と書く人もいますがどちらも同じ意味です。好きな方を使用してください。

変数をポインタとして定義する、例えば、「int *p」とすると、pがアドレスになり、*pがそのアドレスに格納されている値になります。「int a」と変数aを定義した場合は、&aがaが格納されているアドレスでした。下の表を参考にしてください。

int a;(普通の変数定義)int *p;(ポインタの定義)
格納されている値a*p
格納アドレス&ap
ポインタの格納アドレスN/A&p

図にするとこんな感じになります。

ポインタの解説図

左の図は普通の変数の場合。そのまま値を見ています。

右の図はポインタの場合。ポインタをそのまま見る(上のテーブルの例でいうとpを見る)と、参照先のアドレスがかかれています。参照先のアドレスに格納されている値はアスタリスク「*」をつけた値(上のテーブルでは*p)によってアクセスできます。右上のペンギンはp、右下のペンギンは*pの値を見ているわけです。

もう少し身近な例で例えてみた絵を下に示します。

ポインタの解説図

ポインタ定義したaは自由に値を変えることができます。変数を格納するアドレスを自由に変えられるのです。ただし、適当に設定したアドレスにいつでも書き込みや読み込みができるかというと、そうではありません。

上の表から、aを*aに、&aをaに変えれば、そのままプログラムが動きそうです。すなわち、

#include <stdio.h>

int main(){
	int *a;
	*a = 1;
	printf("%d\n",*a);
	return 0;
}

とすれば、1が出力されるプログラムができそうです。しかし、最初にポインタで指定されたアドレスが本当に1を書き込んでよい領域であるか、分かりません。OSが使用している重要な領域かもしれません。普通に変数定義をした場合は書き込み可能な領域を確保してくれますが、ポインタ定義の場合はそんな気の利いたことはしてくれません。書き込んではいけない領域に書き込もうとするとプログラムが予期せぬ動きをしたり、強制終了してしまいます。

身近な例で例える、「ブラクラや危険なサイトにアクセスしないように、リンク先URLを確認するなどして注意する」必要があるということです。

ポインタのセット

ポインタを使う場合はその領域に書き込んでよいのかを十分に注意する必要があります。ポインタの使用の際の手順は必ず、

  1. ポインタの宣言をする。
  2. ポインタを書き込み(読み込み)可能な領域に設定する。
  3. 読み書きする。

となります。重要なのは2番目の手順です。これを守らないと、常に強制終了の恐怖におびえながら使用しなければならない、危険なプログラムになってしまいます(場合によってはOSが落ちることも・・・)。

領域設定の例を簡単に示します。

別変数のアドレスをセットする

一番基本的な使い方です。別変数のアドレスをそのまま使うので、安心です。

#include <stdio.h>

int main(){
	int *a;
	int i;
	a = &i; /* & で アドレス指定です。 */
	/* 処理 */
	return 0;
}

関数の返り値をセットする

変数のアドレスをセットする方法と似ていますが、ちょっと進んだ使い方です。返り値がポインタの関数のみ有効です。ただし、この場合は予想通りの値が入っていない場合もあるので、注意が必要です。NULLポインタチェックをしましょう。(fopen, fcloseは別のページで扱いますので、ここはさらっとながしてください。)

FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);
#include <stdio.h>

int main(){
	FILE *a;
	a = fopen("xxx.txt", "r");
	if(a == NULL){ /* NULLポインタチェック */
		return 1;
	}
	/* 処理 */
	fclose(a);
	return 0;
}

新たに領域を確保する

詳しくはこのページの下の方でやりますので、ここはさらっと流します。

void *malloc(size_t size);
#include <stdio.h>
#include <stdlib.h>  /* mallocを使うのに必要 */

int main(){
	int *a;
	a = (int)malloc(100);
	if(a == NULL){ /* NULLポインタチェック */
		return 1;
	}
	/* 処理 */
	return 0;
}

ポインタを使う

下のプログラムはポインタを別の変数のアドレスに設定して書き込む例です。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b;      /* 変数 */
	a = &b; /* ポインタを変数のアドレスに設定 */
	*a = 1;
	printf("%d %d\n",*a,b);
	return 0;
}

プログラムの最後で、*aとbを出力していますが同じ結果になっています。これは、ポインタの指すアドレスと変数のアドレスが同じであるため、同じ結果が表示されるのです。

故に、次のようにしても同じ結果になります。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b;      /* 変数 */
	a = &b; /* ポインタを変数のアドレスに設定 */
	b = 1;
	printf("%d %d\n",*a,b);
	return 0;
}

ポインタと配列

Page6で配列を扱いました。配列は連続した領域を使用します。なので、a[0]はa[1]の隣のアドレスにあり、a[1]はa[0]とa[2]に挟まれた領域を使用しています。ポインタを、この配列のアドレスに設定してみると、次のようになります。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b[5];   /* 配列 */
	a = &b[0];  /* ポインタを配列の先頭アドレスに設定 */
	b[0] = 1;
	printf("%d %d\n",*a,b[0]);
	return 0;
}

ただの変数から配列にしてみただけです。b[0]=1を*a=1にかえても同じ結果になります。これは普通の変数の場合と同じです。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b[5];   /* 配列 */
	a = &b[1];
	b[0] = 1;
	b[1] = 2;
	printf("%d %d %d\n",*a,b[0],b[1]);
	return 0;
}

配列の2番目のアドレスに設定してみた例です。*aはb[1]と同じ値を表示することが分かります。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b[5];   /* 配列 */
	a = &b[0];
	b[0] = 1;
	b[1] = 2;
	printf("%d %d %d\n",*(a+1),b[0],b[1]);
	return 0;
}

ポインタは配列の先頭アドレスに設定し、最後の表示では、ポインタで設定したアドレスの隣のアドレスの値を表示しています。b[0]の隣はb[1]なので、*(a+1)はb[1]と同じ値が表示されます。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b[5];   /* 配列 */
	a = &b[0];
	b[0] = 1;
	b[1] = 2;
	a++;
	printf("%d %d %d\n",*a,b[0],b[1]);
	return 0;
}

ポインタそのものを移動させた例です。b[0]に設定したポインタを1つずらしているので、最後の表示では*aはb[1]の値と同じ値が表示されます。

なお、次のようにした場合、ポインタは配列の先頭アドレスに設定されます。

#include <stdio.h>

int main(){
	int *a;     /* ポインタ */
	int b[5];   /* 配列 */
	a = b;
	b[0] = 1;
	b[1] = 2;
	printf("%d %d %d\n",*a,b[0],b[1]);
	return 0;
}

*aとb[0]は同じ値が表示されます。

領域確保

ポインタは別の変数のアドレスにセットするだけでなく、独自の空いた領域を確保して、値の書き込み、読み込みをすることができます。

#include <stdio.h>
#include <stdlib.h>

int main(){
	int *a;     /* ポインタ */
	a=(int *)malloc(5 * sizeof(int));
	*a = 1;
	*(a+1) = 2;
	printf("%d %d\n",*a,*(a+1));
	free(a);
	return 0;
}

#include <stdlib.h> というよくわからないものが増えていますが、これはPage8で説明することとして、ここではmallocの処理について説明します。

mallocはメモリの領域を確保する関数です。

void* malloc(size_t [サイズ]);

void*は適当なポインタという意味です。int型のポインタ(int*)が欲しいので、型変換をしています(関数や数値の前に変数型(intやchar、int*など)を置くと、型の変換ができます。詳細はPage18で扱っています)。size_tはint型と同じと考えてください。

この例ではsizeof(int)でint型のサイズが取得できるので、 5 * 4 = 20バイトの領域をaのために確保したことになります。この20バイトの領域は自由に使うことができます。ポインタのセットされたアドレス、その隣のアドレスに書き込みができていることが確認できます。

freeはメモリを開放する関数です。不要になったメモリは開放しましょう。

void free(void* ポインタ)

なお、mallocに失敗すると、NULLが返されます(a=0になります)。この場合は領域が確保されていませんので、書き込みをするとエラーになります。

また、領域が足りなくなった場合はreallocで領域の拡張ができます。

#include <stdio.h>
#include <stdlib.h>

int main(){
	int *a;     /* ポインタ */
	a=(int *)malloc(5 * sizeof(int));
	*a = 1;
	a=(int *)realloc(a, 10 * sizeof(int));
	*(a+9) = 2;
	printf("%d %d\n",*a,*(a+9));
	free(a);
	return 0;
}

最初20バイトの領域を確保し、途中で40バイトに拡張しています。reallocで使用する値(引数といいます)は2つで、ポインタとサイズです。

void* realloc(void* ポインタ, size_t サイズ)

ポインタのポインタ

「int **a;」とすれば、ポインタのポインタが定義できます。

int a;(普通の変数)int *a;(ポインタ)int **a;(ポインタのポインタ)
格納されている値a*a**a
格納アドレス&aa*a
ポインタの格納アドレスN/A&aa
ポインタのポインタの格納アドレスN/AN/A&a

ポインタも、それを格納しているアドレスがありますが、ポインタのポインタを使用すればそれに対するポインタを設定することができます。

ポインタのポインタという表現が分かりにくいですが、絵にかくとこんな感じです。普通のポインタが分かればその延長線だということが分かります。

ポインタのポインタの説明

(特別出演:重音テト 様)

様々なポインタの使用例をまとめたソースコードをここに載せました。勉強などに使ってください。


戻る