ポインタに関する考察

自分はもう、ポインタについて理解したと思っていましたが、昨日今日とプログラミングしてみて全然分かってないということが分かったので、自分なりにメモしておこうと思います。もし間違ってるところがあれば指摘いただけるとうれしいです。

参考

  1. ポインタ虎の巻

上のサイトを主に参考にしました。最初の「ポインタと配列の違い」はこのサイトの最初の部分をまとめたものです。この記事よりも全然詳しく、深い内容が書いてあります。
また、二次元配列とダブルポインタの関係は以下のサイトを参考にしました。

  1. http://ik.am/blog/2006/11/07/doublepointer/

ポインタと配列の違い

ポインタを配列は似ていますが、書き方が二つあるということは、当然違います。

	char hoge[] = "hoge";
	char *hoge2 = "hoge";

というものがああるとします。このとき、hoge2はポインタなので、値としてchar型の変数が格納されているアドレスをもつことができ、このアドレスは変更する事が可能です。しかし、hogeは配列そのものを表し、hogeは配列の先頭のアドレスと等しくなります。そのため、hoge自体の値を変更する事はできません。つまり、

	char *foo = "foo";
	hoge = foo;			/* エラー */
	hoge2 = foo;		/* 可能   */

です。hogeは配列の先頭のアドレスに変換されますから、 hoge = foo; という文は数値に数値を代入しようとするのと同じようなものですから、当然エラーになります。また、

	extern char *foo;
	char *p;
	p = foo;

となっているとき、

	char *foo = "foo";

となっているといいですが、

	char foo[] = "foo";

となっていると、gccでは

error: conflicting types for ‘str2’
note: previous definition of ‘str2’ was here

という風にエラーがでます。コードだけ見るとなんとなく両方とも正しいように思えますが、コンパイラはきちんと配列とポインタを区別しているということです。

あるポインタを受けとってそのポインタ自身の値を変える関数

例として、ある文字列へのポインタと任意の文字cを受けとって、その文字列へのポインタを最初に現れた文字cのアドレスに変更する関数を作ります。
自分は初め、次のように書きました。

#include <stdio.h>

void hoge(char *x,char c){
	while(*x != '\0'){
		if(*x == c){
			return;
		}
		x++;
	}
	x = NULL;
}

int main(){
	char foo[] = {"foohoge"};
	char *p;

	p = foo;
	printf("%p\n",p);
	hoge(p,'h');
	printf("%p\n",p);
	
	return 0;
}

ですが、この関数は意図したようには動きません。なぜなら、この関数のxで受けとるのはあくまでも「文字列の先頭のアドレス」であって、間接演算子(*)によって文字列の文字を変更するはできますが、いくら関数内でxの値を変更しても呼び出し元のポインタの数値を変更できるわけではないからです。
実際、自分の環境で上のプログラムを実行するとこうなりました。

0xbf9488b4
0xbf9488b4

xその物の値(=文字列へのアドレス)を変更するためには、xそのもののアドレス(=&x)を関数に渡してあげなければならないのです。つまり、関数の呼び出しは次のようになります。

	hoge(&p,'h');

そして、これに対応する用に関数を書き直すとこうなります。

void hoge(char **x,char c){
	while(**x != '\0'){
		if(**x == c){
			return;
		}
		(*x)++;
	}
	*x = NULL;
}

上の関数で、xは文字列へのポインタが入っているメモリのアドレス(つまりpのアドレス)を受けとります。ここで、*xはpに代入されている値(=文字列fooの先頭のアドレス)であって、**xはその文字列fooの先頭のアドレスで示された場所に入っている値(='f')を差します。こうすることで、*xの値を変更すれば結果的にpの値を書き換えることができるようになります。ここで注意しなければならないのは、*は++よりも優先度が低いので、もし、

	*x++;

のように書いてしまうと、xそのももの値がポインタ演算されてしまい、結果的にxが全然違うところを指してしまうようになります(自分はここで少しはまりました)。この関数を実行すると次のようになりました。

	0xbf924b34
	0xbf924b37

きちんとpの値が変更されていることが分かります。


二次元配列とポインタの関係

まず、次のような宣言の違いについて。

	int *hoge[5];
	int (*foo)[5];

この場合、hogeは「要素数が5つで値がint型のポインタである配列」です。それに対して、fooの方は、「int型で要素数が5である配列へのポインタ」です。

	int x[2][10];

というint型の2次元配列があったときに、この配列をさすポインタを宣言するには、

	int (*p)[10]; /* 要素数が10のint型配列へのポインタ */
	p = x;

としなければなりません。pは「要素数が10のint型配列」へのポインタなので、int型が4バイトのとき、p+1のポインタ演算の結果は、p + 4*10 となります。xそのものが表すのはあくまでもxの先頭のアドレスなので、

	int **p;
	p = x;

は明らかに間違いです。つまり、1次元配列のときは

	int x[5];
	int *p;
	p = x;

とできますが、2次元配列になったからといって int **p; にすればいいものではないのです。実際、int x[2][10]という配列は、int型の要素が10*2個並んだもので、x[0]は配列の先頭のアドレスを、x[1]は配列の先頭のアドレス + 10*4を表します。もしも、ポインタの配列にxを代入したいというときは、

	int *p2[2];		/* 要素数が2で値がint型のポインタである配列 */
	p2[0] = x[0];
	p2[1] = x[1];

となります。また、

	int *p3;
	p3 = x;

という風にもできます(警告がでます)。このときは、p3はxの先頭のアドレスを指し、p3[1]は単に p + 4 のアドレスを指します(p3は単に一次元配列へのポインタとなっている)。確認のために次のようなプログラムを作ってみます。

#include <stdio.h>
int main(int argc, char const* argv[])
{
	int x[2][10] = {{10,11,12,13,14,15,16,17,18,19},{20,21,22,23,24,25,26,27,28,29}};
	int (*p)[10];
	int *p2[2];
	int *p3;
	int i,j;

	p = x;
	p2[0] = x[0];
	p2[1] = x[1];
	p3 = x;

	printf("%p %p\n",p,p+1);
	printf("%p %p\n",p2[0],p2[1]);
	printf("%p %p\n",&p3[0],&p[1]);
	printf("%p %p\n",x[0],x[1]);

	for(i = 0;i < 2;i++){
		for(j = 0;j < 10;j++){
			printf("%d ",p[i][j]);
		}
		printf("\n");
		for(j = 0;j < 10;j++){
			printf("%d ",p2[i][j]);
		}
		for(j = 0;j < 10;j++){
			printf("%d ",p3[i*10 + j]);
		}
		printf("\n\n");
	}
	return 0;
}

実行結果は次のようになります。

0xbfbcb1e8 0xbfbcb210	/* p,p+1 の値 */
0xbfbcb1e8 0xbfbcb210	/* p2[0],p2[1] の値 */
0xbfbcb1e8 0xbfbcb1ec	/* &p3[0],&p3[1] の値 */  <-- &p3[1] は &p3[0] + 4
0xbfbcb1e8 0xbfbcb210	/* x[0],x[1] の値 */
10 11 12 13 14 15 16 17 18 19 
10 11 12 13 14 15 16 17 18 19 
10 11 12 13 14 15 16 17 18 19 

20 21 22 23 24 25 26 27 28 29 
20 21 22 23 24 25 26 27 28 29 
20 21 22 23 24 25 26 27 28 29 


二次元配列を関数に渡す場合に関数は、

int hoge(int x[][10]);

もしくは

int hoge(int *(x)[10]);

と宣言しなければなりません。なぜなら、x[2][10]となっていたとき、x[1]は「先頭のアドレス+10*4」を参照するように、2番目の要素数が分からなければ要素を指定することができないからです。当然

int hoge(int **x);

は間違いです。

二次元配列とダブルポインタの関係

もし、二次元配列をダブルポインタで表したいときは、次のようにします。

	int a[3][2] = {{10,11},{20,21},{30,31}};
	int *p[3];
	int **pp;
	int i;

	for(i = 0;i < 3;i++) p[i] = a[i];
	 
	pp = p;

このとき pp には 配列pのの先頭のアドレス=p[0]のアドレスが代入されます。つまり、*ppでアドレスを、**ppで値をいじれるようになります。このあたりはhttp://ik.am/blog/2006/11/07/doublepointer/が詳しいです。試しに次のようなプログラムを実行してみます。

#include <stdio.h>
int main(){
	int a[3][2] = {{10,11},{20,21},{30,31}};
	int *p[3];
	int **pp;
	int i,j;

	for(i=0;i < 3;i++) p[i] = a[i]; 

	pp = p;


	printf("%p,%p\n",&a,a);
	printf("%p,%p,%p\n",&p,p,p[0]);
	printf("%p,%p,%p\n",&pp,pp,pp[0]);

	printf("\n");

	for(i = 0;i < 3;i++){
		for(j = 0;j < 2;j++){
			printf("%p,%p,%d\n",p[i],&p[i][j],p[i][j]);
		}
	}
	printf("\n");
	for(i = 0;i < 3;i++){
		for(j = 0;j < 2;j++){
			printf("%p,%p,%d\n",pp[i],&pp[i][j],pp[i][j]);
		}
	}

	printf("\n");

	printf("%p,%p:%d,%p:%d\n",pp,*pp,*(*pp),*pp+1,*(*pp+1));
	printf("%p,%p:%d,%p:%d\n",pp+1,*(pp+1),*(*(pp+1)),*(pp+1)+1,*(*(pp+1)+1));
	printf("%p,%p:%d,%p:%d\n",pp+2,*(pp+2),*(*(pp+2)),*(pp+2)+1,*(*(pp+2)+1));
	return 0;
}

実行結果は次のようになります。

0xbf841440,0xbf841440				/* aのアドレス、aの値(=aの先頭のアドレス) */
0xbf841458,0xbf841458,0xbf841440	/* pのアドレス、pの値(=pの先頭のアドレス)、p[0]の値(=a[0]のアドレス) */
0xbf84146c,0xbf841458,0xbf841440	/* ppのアドレス、ppの値(=pの値)、pp[0]の値(=p[0]の値) */

0xbf841440,0xbf841440,10	/* p[0]の値(=a[0]のアドレス)、p[0][0]のアドレス(=&a[0]+0)、p[0][0]の値(=a[0][0]の値) */
0xbf841440,0xbf841444,11	/* p[0]の値(=a[0]のアドレス)、p[0][1]のアドレス(=&a[0]+4)、p[0][1]の値(=a[0][1]の値) */
0xbf841448,0xbf841448,20	/* p[1]の値(=a[1]のアドレス)、p[1][0]のアドレス(=&a[1]+0)、p[1][0]の値(=a[1][0]の値) */
0xbf841448,0xbf84144c,21    /* p[1]の値(=a[1]のアドレス)、p[1][1]のアドレス(=&a[1]+4)、p[1][1]の値(=a[1][1]の値) */
0xbf841450,0xbf841450,30	/* p[2]の値(=a[2]のアドレス)、p[2][0]のアドレス(=&a[2]+0)、p[2][0]の値(=a[2][0]の値) */
0xbf841450,0xbf841454,31	/* p[2]の値(=a[2]のアドレス)、p[2][1]のアドレス(=&a[2]+4)、p[2][1]の値(=a[2][1]の値) */

0xbf841440,0xbf841440,10	/* pp[0]の値(=p[0]の値)、pp[0][0]のアドレス(=p[0][0]のアドレス)、pp[0][0]の値(=p[0][0]の値) */
0xbf841440,0xbf841444,11	/* pp[0]の値(=p[0]の値)、pp[0][1]のアドレス(=p[0][1]のアドレス)、pp[0][1]の値(=p[0][1]の値) */
0xbf841448,0xbf841448,20	/* pp[1]の値(=p[1]の値)、pp[1][0]のアドレス(=p[1][0]のアドレス)、pp[1][0]の値(=p[1][0]の値) */
0xbf841448,0xbf84144c,21	/* pp[1]の値(=p[1]の値)、pp[1][1]のアドレス(=p[1][1]のアドレス)、pp[1][1]の値(=p[1][1]の値) */
0xbf841450,0xbf841450,30	/* pp[2]の値(=p[2]の値)、pp[2][0]のアドレス(=p[2][0]のアドレス)、pp[2][0]の値(=p[2][0]の値) */
0xbf841450,0xbf841454,31	/* pp[2]の値(=p[2]の値)、pp[2][1]のアドレス(=p[2][1]のアドレス)、pp[2][1]の値(=p[2][1]の値) */

0xbf841458,0xbf841440:10,0xbf841444:11 /* ppの値(=pの先頭のアドレス)、ppの値が指すアドレスの内容(=aの先頭のアドレス)、ppの値が指すアドレスが指す値の内容(=a[0][0])、ppの値が指すアドレスの次のアドレス(&a[0] + 4)、a[0][1] */
0xbf84145c,0xbf841448:20,0xbf84144c:21 /* ppの値+4(p[1]のアドレス)、p[1]の値が指すアドレスの内容(=&a[1] =&a[0] + 4*2)、p[1]の値が指すアドレスが指す値の内容(=a[1][0])、p[1]の値が指すアドレスの次のアドレス(&a[1] + 4)、a[1][1] */
0xbf841460,0xbf841450:30,0xbf841454:31 /* ppの値+4*2(p[2]のアドレス)、p[2]の値が指すアドレスの内容(=&a[2] =&a[0] + 4*2*2)、p[2]の値が指すアドレスが指す値の内容(=a[2][0])、p[2]の値が指すアドレスの次のアドレス(&a[2] + 4)、a[2][1] */


こうやって色々見てきましたが、かなりややこしいと思います。最初にも書きましたが、間違っていたら教えていただけるとうれしいです。