アドレスの概要

アドレスの概要


サイト全体の目次

稿


稿

アドレス

(untitled)

これまで使ってきた箱「変数」はメモリ上に作られます。そしてメモリは1バイトごとに管理されており、一直線に並んでいて、1バイトごとに住所がついています。その住所のことを「アドレス」といいます。住所といってもつながった数値で識別されているだけですから、感覚としては小学校の頃やった数直線のような感じです.

この数字がアドレス(住所)
       V
-------------------------------------------------------
...|  236  |  237  |  238  |  239  |  240  |  241  |...
-------------------------------------------------------
   |<----->|
     1byte
	

変数を宣言するとこのメモリ上に自動的に型と同じだけのバイト数が確保されます。「int」なら4バイトですね.

int x;    と宣言すれば、

   |<------------  x  ------------>|
-------------------------------------------------------
...|  236  |  237  |  238  |  239  |  240  |  241  |...
-------------------------------------------------------
   |<---------  4bytes  ---------->|
				

感覚としては変数名が表札で、アドレスが住所です。普段はこの表札のおかげで住所を気にすることなく変数が使えるわけです。

アドレス演算子

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int x;
/*06*/ 
/*07*/   printf("変数 x のアドレス -> %d\n", &x);
/*08*/ 
/*09*/   return 0;
/*10*/ }

(untitled)

場合によってはそのアドレス(住所)を知りたいときもあります。そんなときに使うのが「&」記号で表されるアドレス演算子で、変数名の前につけることで、その変数のアドレスがわかります。

list-3.1.1 を実行すると想像外の桁数の数がでてきたかもしれません。ですが、それが変数 x のアドレスなのです。ここで気にかけてほしいのは、アドレスといっても結局は数値ですから、変換文字は「%d」になるということです。

ポインタ

このセクションのソース

(untitled)

さて、そのアドレスを保存しておくことのできる変数もあります。ポインタ変数といって、この変数の中に入れられた数値はアドレスと見なされます。(何度も繰り返しますが、アドレスとは結局のところ数値であることを忘れないでください。)ポインタ変数は「型名 *変数名」の形で宣言されます。変数名には好きな名前を付けることができますが、型はこれから入れる変数の型と一致していなければなりません。例えば「int」型変数のアドレスを入れようと思ったら「int *変数名」のように宣言します。

宣言した後は普通の変数と同じ感覚で使えます。「*」記号もつける必要はありませんから、初期化する時には list-3.1.2 のように書きます。ただしこの例の場合、「x」の「アドレス」を代入したいのですから、「&x」とします。これで、ポインタ変数「y」には「x」の「アドレス」が入ったわけです。また、宣言と同じに初期化することもでき、その場合は

int *y = &x

のように書きます。

間接演算子

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int x = 10;
/*06*/   int *y;
/*07*/ 
/*08*/   y = &x;
/*09*/   printf(" *y の指すアドレスは %d で、その中身は %d です。\n", y, *y);
/*10*/ 
/*11*/   return 0;
/*12*/ }

(untitled)

さて、ポインタ変数にアドレスは入りましたが、「アドレスの指す先の中身を見たい」時はどうするのでしょう。そんな時は間接演算子「*」記号を使います。ポインタ変数として宣言したものに「*」を付けると、アドレスの指す先の中身にアクセス(見たり、変更したり)出来ます。.

こうすると、y の指すアドレスは 236 になります。

   |<-------  x  (4bytes)  ------->|
-------------------------------------------------------
...|  236  |  237  |  238  |  239  |  240  |  241  |...
-------------------------------------------------------
   |                              /
   | /---------------------------/
   |/
  ---------    ポインタ変数 y の指すアドレスは 236 ですが、
*y|  236  |    int 型のポインタとして宣言しているため
  ---------    自動的に int 型としてアクセスしてくれます。
  	

list-3.1.3 だと、「y」には「x」のアドレスが入っているので、「*y」は、あたかも「x」と書いているかのような扱いになります。極端な話し「*y」と書くのと、「x」と書くのとは同じことなのです。ですから上の例の場合、「x」は「10」ですから、

printf(" *y の指すアドレスは %d で、その中身は %d です。\n", y, *y);

とすれば、「x」のアドレスと中身の「10」が表示されますし、
*y = 20;
printf("変数 x の値は %d です。", x);

とすれば、「x = 20」としたのと同じですから printf 文では「20」が表示されます。また、次の例はすでに *y に入っている値をインクリメントします。
(*y)++;

これは「x++;」と同じことです。ですが、この時注意して欲しいのは「*y++;」のように書くと演算順位の関係で、y に入っているアドレス値そのものをインクリメントしてしまう(すなわち、別の場所を見てしまう)ので、カッコを書いてください。なお、ポインタ変数も変数の一種ですから、自由な代入が出来ます。

ポインタの入れ替え

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int x = 10, y = 20;
/*06*/   int *p1 = &x, *p2 = &y, *ptemp;
/*07*/ 
/*08*/   printf(" x のアドレスは %d で、その中身は %d です。\n", &x, x);
/*09*/   printf(" y のアドレスは %d で、その中身は %d です。\n" ,&y, y);
/*10*/   printf("\n");
/*11*/   printf(" p1 の指すアドレスは %d で、その中身は %d です。\n", p1, *p1);
/*12*/   printf(" p2 の指すアドレスは %d で、その中身は %d です。\n", p2, *p2);
/*13*/   printf("\n");
/*14*/ 
/*15*/   ptemp = p1;
/*16*/   p1 = p2;
/*17*/   p2 = ptemp;
/*18*/ 
/*19*/   printf("ポインタを入れ替えました。\n");
/*20*/   printf(" p1 の指すアドレスは %d で、その中身は %d です。\n", p1, *p1);
/*21*/   printf(" p2 の指すアドレスは %d で、その中身は %d です。\n", p2, *p2);
/*22*/ 
/*23*/     return 0;
/*24*/ }

(untitled)

list-3.1.4 ではポインタ変数の中身を入れ替えて、すなわち指す先を入れ替えています。実行すると確かに指す先が変わっているはずです。ですが「指す先」を変えただけですから、「x」「y」の中身は変わっていません。変えたい時はどうするか分かりますね?「*p1」などとして処理すればいいのです。ところで、scanf(”%d”, &x) の「&」記号、実はアドレス演算子なのです。お分かりになったでしょうか?つまり、scanf 関数は「入力されたデータを指定されたアドレスに入れろ」という関数なのです。ですから、「&x」と書く代わりに、「x」のアドレスを入れたポインタ変数を書いてもいいわけです。

今回やった内容だけでは、何の利点も無いように思えるかもしれませんが、アドレスの概念は C言語 にとって重要なものですし、少しづつその利用価値が分かってくると思います。ですから、今回の内容はしっかりと理解しておいてください。

最後に、あまり使うことはないでしょうがポインタ変数も型変換(型キャスト)することができます。

char x;
char *y = &x;
int *z;

z = (int *)y;    /*char 型ポインタを int 型ポインタにキャスト*/

書式は見てのとおり「(変換したい型 *)」です。この例ならば、ポインタ変数 y に入っている char 型ポインタは、int 型ポインタにキャストされて int 型ポインタ変数 z に代入されます。ですが、この場合も変数 y の型自体が変換されるわけではありません。なお、ポインタ変数に入っているものは結局のところアドレス、すなわち整数なわけですから、「(int)y」のように int 型へキャストすれば、アドレスを普通の int 型変数に格納することもできます。

ポインタのポインタ

このセクションのソース

/*01*/ #include <stdio.h>
/*02*/ 
/*03*/ main()
/*04*/ {
/*05*/   int x, *y, **z;
/*06*/ 
/*07*/   x = 10, y = &x, z = &y;
/*08*/ 
/*09*/   printf(" x のアドレスは %d で、その中身は %d です。\n", &x, x);
/*10*/   printf(" y の指すアドレスは %d で、その中身は %d です。\n", y, *y);
/*11*/   printf("\n");
/*12*/   printf(" y のアドレスは %d で、その中身は %d です。\n", &y, y);
/*13*/   printf(" z の指すアドレスは %d で、その中身は %d です。\n", z, *z);
/*14*/   printf("\n");
/*15*/   printf("また z の指すアドレスの中身に入っている\n");
/*16*/   printf("アドレスの指す中身は %d です。\n", **z);
/*17*/ 
/*18*/   return 0;
/*19*/ }

(untitled)

アドレスを入れておくポインタ変数ですが、ポインタ変数も変数である以上、メモリ上に領域が確保されているはずです。ということは、ポインタ変数のアドレスを操作することもできるはずですね。変数を宣言するときに「**」をつけることでポインタ変数のアドレスを入れるポインタ変数となります。

NULL

(untitled)

さて、ポインタ変数も宣言された直後は、その値は不定なのですが、かといってとりあえず 0 で初期化するというのはいい方法ではありません。そこで C言語 ではどこも指していないアドレスという意味の定数 NULL (ヌル)が用意されています。

int *x = NULL;

NULL の値はコンパイラによって異なるのですが、結局のところ 0 と定義されているケースが多いようです。しかし、そうでない可能性もあるので、NULL を使うのが安全でしょう。また、ポインタを戻り地として返す関数は、エラー時にこの NULL を返すケースがあるので、それによってエラーを判別することができます。

練習問題

問題-3.1.1

何か変数を宣言し、実際にそのアドレスを確認してください。

問題-3.1.2

次のプログラムを理解し、他の利用者に分かりやすいよう、printf 出力に説明を加えてください。必要なら、printf を増やしても構いません。また、このプログラムはこのままだと誤操作をした際にシステムを破壊する危険性があります。その理由を述べ、回避策をプログラムに加えてください。最後に、プログラムの動作が他の人にも分かるようにコメントを入れてください。

#include <stdio.h>

main()
{
	int x, y, p;

	x = 1;	y = 2;
	printf("%d %d\n", x, y);
	printf("%d %d\n", &x, &y);
	scanf("%d", &p);
	scanf("%d", p);
	printf("%d %d\n", x, y);

	return 0;
}

問題-3.1.3

次の二つのプログラムは、変数 x と y にそれぞれ初期値 5、10 を設定し、選択されたほうの変数の値を
足す 1、引く 2、かける 3、割る 4 するプログラムです。実行した場合の動作は二つともまったく同じですが、ソースの中身を理解し、比較検討してください。

#include <stdio.h>

main()
{
	int x, y, sw;

	x = 5;	y = 10;
	printf("値:x = %d, y = %d\n", x, y);
	printf("どちらの変数を演算しますか?\n");
	printf("    x -> 1, y -> 2:");
	scanf("%d", &sw);
	switch(sw){
	case 1:
		x += 1;
		printf(" 1   足しました:%d\n", x);
		x -= 2;
		printf(" 2   引きました:%d\n", x);
		x *= 3;
		printf(" 3   かけました:%d\n", x);
		x /= 4;
		printf(" 4 で割りました:%d\n", x);
		break;
	case 2:
		y += 1;
		printf(" 1   足しました:%d\n", y);
		y -= 2;
		printf(" 2   引きました:%d\n", y);
		y *= 3;
		printf(" 3   かけました:%d\n", y);
		y /= 4;
		printf(" 4 で割りました:%d\n", y);
		break;
	default:
		printf("正しい値が選択されませんでした。\n");
	}

	return 0;
}
#include <stdio.h>

main()
{
	int x, y, sw, *p;

	x = 5;	y = 10;
	printf("値:x = %d, y = %d\n", x, y);
	printf("どちらの変数を演算しますか?\n");
	printf("    x -> 1, y -> 2:");
	scanf("%d", &sw);
	switch(sw){
	case 1:
		p = &x;
		break;
	case 2:
		p = &y;
		break;
	default:
		printf("正しい値が選択されませんでした。\n");
		/* goto で処理せずに、ここで return 0 としても
		よい。詳しくは「自作の関数」の章参照。 */
		goto end;
	}
	*p += 1;
	printf(" 1   足しました:%d\n", *p);
	*p -= 2;
	printf(" 2   引きました:%d\n", *p);
	*p *= 3;
	printf(" 3   かけました:%d\n", *p);
	*p /= 4;
	printf(" 4 で割りました:%d\n", *p);
end:
	return 0;
}

問題-3.1.4

もう一度 List 11-4 を確認し、自分で普通の変数とポインタ変数を用意していろいろと試してみてください。

練習問題回答例

問題-3.1.1

#include <stdio.h>

main()
{
	int x;

	printf("x のアドレスは %d です。\n", &x);

	return 0;
}

問題-3.1.2

この問題はほとんど実用的でありませんが、アドレスを理解するのに重要です。よく理解してください。

#include <stdio.h>

main()
{
	int x, y, p;

	x = 1;	y = 2;
	/* x, y の現在の値を表示します。 */
	printf("現在の値:x = %d, y = %d\n", x, y);

	/* 変数 x, y のアドレスを表示します。 */
	printf("x, y のアドレス:x -> %d, y -> %d\n", &x, &y);

	/* 値を変更したい変数(x or y)のアドレスを整数型として直接
	入力し、整数型変数 p にその値(アドレス)を格納します。 */
	printf("値を変更したい変数のアドレスを入力してください:");
	scanf("%d", &p);

	/* 変更後のアドレスを scanf で読み取ります。この際、scanf の
	第二引数には変更したい変数のアドレス、即ち今入力した p を
	書きます。つまりもし x のアドレスが 120 で、p に 120 という
	整数値が入っていれば(入力したならば)scanf によって 120 番地
	に、即ち変数 x に変更したい値が格納されます。
	ただし、ここにプログラムがシステムを破壊する危険性があります。
	もし上の scanf で p に x のアドレスでも y のアドレスでも無い
	値を入力してしまった場合、全然関係の無いアドレス(そのアドレス
	は他のプログラムが使っているかもしれない)の値が変更されて
	しまいます。そこで、if 文を使い、p が x のアドレスでも y の
	アドレスでもない場合は警告を表示させて終了します。この際、
	「p != &x」のようにすると、コンパイラが「p と x は型が違うけど、
	この評価式はこれでよいの?」と警告を出してくるかもしれないので、
	「p != (int)&x」もしくは「(int *)p != &x」のように型変換を行う
	とよいでしょう。 */
	if(p != (int)&x && p != (int)&y){
		printf("アドレスが正しくありません。\n");
	}else{
		printf("いくつに変更したいですか?:");

		/* コンパイラが警告を出すのなら、ここの p も「(int *)p」
		のように型変換をすればよい。 */
		scanf("%d", p);
		printf("変更後の値:x = %d, y = %d\n", x, y);
	}

	return 0;
}

問題-3.1.3

最初のソースは x と y を選択したら、それぞれの変数用に同じ処理手順を用意しているので記述に無駄があります。しかし、二つ目のソースではポインタを利用して、「ポインタの指す先を操作する」方法を取っているため、本質的な処理手順は一つ書くだけですみます。また、二つ目のソースと比べてから最初のソースを考えると、もう一つ変数を用意して、それに値を代入してから処理するという方法も思いつくと思いますが、それぞれの変数の値を変更したい場合はポインタを使ったほうが「変数を直接操作している」点ですっきりします。

問題-3.1.4

省略


読み込み中・・・
10秒待っても表示が変わらない場合、次の理由が考えられます。
・Javascript が無効になっています。
・検索エンジンのキャッシュを見ています。
サイトホームへ / 上位ページへ / ページトップへ / PAROFトレンドショッピングへ
Copyright (C) 2010 totobon all right reserved.