これまで使ってきた箱「変数」はメモリ上に作られます。そしてメモリは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*/ }
場合によってはそのアドレス(住所)を知りたいときもあります。そんなときに使うのが「&」記号で表される
list-3.1.1 を実行すると想像外の桁数の数がでてきたかもしれません。ですが、それが変数 x のアドレスなのです。ここで気にかけてほしいのは、アドレスといっても結局は数値ですから、変換文字は「%d」になるということです。
さて、そのアドレスを保存しておくことのできる変数もあります。
宣言した後は普通の変数と同じ感覚で使えます。「*」記号もつける必要はありませんから、初期化する時には 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*/ }
さて、ポインタ変数にアドレスは入りましたが、「アドレスの指す先の中身を見たい」時はどうするのでしょう。そんな時は
こうすると、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);
*y = 20;
printf("変数 x の値は %d です。", x);
(*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*/ }
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 型ポインタにキャスト*/
/*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*/ }
アドレスを入れておくポインタ変数ですが、ポインタ変数も変数である以上、メモリ上に領域が確保されているはずです。ということは、ポインタ変数のアドレスを操作することもできるはずですね。変数を宣言するときに「**」をつけることでポインタ変数のアドレスを入れるポインタ変数となります。
さて、ポインタ変数も宣言された直後は、その値は不定なのですが、かといってとりあえず 0 で初期化するというのはいい方法ではありません。そこで C言語 ではどこも指していないアドレスという意味の定数 NULL (ヌル)が用意されています。
int *x = NULL;
何か変数を宣言し、実際にそのアドレスを確認してください。
次のプログラムを理解し、他の利用者に分かりやすいよう、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;
}
次の二つのプログラムは、変数 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;
}
もう一度 List 11-4 を確認し、自分で普通の変数とポインタ変数を用意していろいろと試してみてください。
#include <stdio.h>
main()
{
int x;
printf("x のアドレスは %d です。\n", &x);
return 0;
}
この問題はほとんど実用的でありませんが、アドレスを理解するのに重要です。よく理解してください。
#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;
}
最初のソースは x と y を選択したら、それぞれの変数用に同じ処理手順を用意しているので記述に無駄があります。しかし、二つ目のソースではポインタを利用して、「ポインタの指す先を操作する」方法を取っているため、本質的な処理手順は一つ書くだけですみます。また、二つ目のソースと比べてから最初のソースを考えると、もう一つ変数を用意して、それに値を代入してから処理するという方法も思いつくと思いますが、それぞれの変数の値を変更したい場合はポインタを使ったほうが「変数を直接操作している」点ですっきりします。
省略