ポインタ
メモリ
メモリとは変数や配列の値を格納する領域のことです。
メモリ領域は1バイト単位で確保できます。
例えば「int aaa = 10;」を宣言した場合はメモリ領域から
int型のサイズ分(4バイト)空いている場所を探し、そこに10を格納します。
アドレス
アドレスとはメモリ上の位置を示すための連続した番号のことです。
先ほどのメモリのイメージ図にアドレス情報をつけてみたいと思います。
変数宣言を行うことで、aaaの保存領域がメモリ内に確保されます。
aaaのメモリに確保した保存領域は1000~1003までですが、
このままでは表現がしづらいので、先頭のアドレスを
その変数のアドレスとして扱っています。
今回の変数aaaのアドレスは1000ということになります。
アドレスの表記方法
プログラムでは変数名の頭に&(アンパサント)をつけると
その変数のアドレスを表します。
具体例
int aaa;
int bbb;
printf("aaaのアドレスは%d", &aaa);
printf("bbbのアドレスは%d", &bbb);
※上の具体例の結果は人によって割り当てられる領域が異なります。
ポインタ
ポインタとはアドレスを値として扱う変数のことです。
ポインタにも型の区別があって、int型のポインタ、char型のポインタなどがあります。
もちろんポインタも変数扱いになりますのでメモリ内に保存場所が確保されます。
書式
ポインタ宣言の書式例
型名 *変数名;
具体例
int *p;
ポインタ変数に変数のアドレスを代入する方法
ポインタ変数へのアドレスの代入は通常の変数と同様に「=」を使用します。
代入された値はアドレスとして扱われますので注意して下さい。
以下は変数のアドレスをポインタに代入した場合の具体例とイメージ図です。
具体例とイメージ図の変数とアドレスの値はイメージしやすいように
同じ内容にしています。
アドレス代入の例
int aaa; // int型の変数aaa ①
int *p; // int型のポインタp ①
p = &aaa; // 変数aaaのアドレスを代入 ②
printf("aaaのアドレス = %d, pの値 = %d\n", &aaa, p);
実行結果:
aaaのアドレス = 1000
pの値 = 1000
ポインタによるアドレスの中身の参照
ポインタ型の変数の前に*をつけると、そのポインタに格納されている
アドレスの中身を参照や変更することができます。
参照の例
// int型の変数を宣言
int aaa; // ①
// int型のポインタを宣言
int *p; // ①
// aaaを0で初期化
aaa = 0;
// int型のポインタ p にaaaのアドレス(1000)を代入する
p = &aaa; // ②
// *をつけることでpのアドレスに格納されている変数の中身を
// 参照することができる
printf("*p = %d\n", *p); // ③
実行結果
*p = 0
なぜ上の具体例の実行結果で0が表示されるのかというと
printfで表示された時のint型のポインタpにはaaaのアドレスが代入されています。
アドレスとは変数の値が保存されているメモリ領域の番地のことです。
pの中にはaaaのアドレスが入っており、aaaには0が入っています。
このポインタ型の変数pに*をつけることで、aaaのアドレスに格納されている値を
参照、変更することが可能になります。
つまりアドレスを通して間接的にaaaの値を参照や変更することが可能ということです。
aaaには0が入っているので*pでaaaのアドレスに格納されている値を
printfで表示した場合、0という値が表示されるというわけです。
ポインタによる参照先の中身の変更
ポインタに格納されているアドレスの中身は参照だけではなく変更も可能です。
変更の例
// int型の変数を宣言
int aaa; // ①
// int型のポインタを宣言
int *p; // ①
// aaaを0で初期化
aaa = 0;
// int型のポインタ p にaaaのアドレス(仮に1000)を代入する
p = &aaa; // ②
// pを通してaaaに10を代入する
*p = 10; // ③
printf("*p = %d\n", *p);
実行結果
*p = 10
具体例ではpのポインタはaaaのアドレスを格納しています。
そのaaaのアドレスを格納しているpに対して*をつけることで
aaaのアドレスの中身を参照することができます。
そして*pに対して代入演算子「=」を使用することで
pに格納されているアドレスの中身である変数aaaの値を変更することが可能です。
NULLポインタ
ポインタは必ず参照先を指定しなければいけません。
ですが、宣言後すぐに参照先を指定できるとは限りません。
そのような場合は、使用する時がくるまでポインタ変数が
未使用と認識できるように初期化を行う必要があります。
ポインタ変数の初期化にはNULLポインタを使用します。
NULLポインタは自分自身(NULLポインタ)以外のいかなるアドレスと比較しても
等しくならないという特性を持った値です。
この特性を利用してポインタの初期化を行う場合はNULLポインタで初期化します。
初期化例
int *p = NULL;
必ずNULLポインタで初期化しているという前提があると
次のソースのようにポインタが有効か無効かを判断できます。
NULLポインタの初期化例
// int aaaを宣言し、0で初期化する
int aaa = 0;
// int型のポインタp1を宣言しNULLで初期化する
int *p1 = NULL;
// int型のポインタp2を宣言しaaaのアドレスを代入する
int *p2 = &aaa;
if (p1 != NULL)
{
printf("p1 = %d\n", p1);
}
if (p2 != NULL)
{
printf("p2 = %d\n", p2);
}
実行結果:
aaaのアドレスが表示
ポインタと配列の関係
配列で要素を含まない配列の名前そのものは、配列の最初の要素を
指し示すポインタの役割をしています。
例えば int a[4]; の場合、aはa[0]の先頭アドレスを指し示しています。
配列のメモリ確保は必ず配列の先頭から連続して並べられます。
なので、配列のアドレスに対して、型のサイズ分を加算をしたら
次の要素を指し示すアドレスになります。
配列の要素を参照する[添え字]は、「配列の頭から添え字分のポインタをずらして
アドレスの中身を参照している」という内容を分かりやすい書式にしたものです。
配列の値を添え字とポインタずらしによる変更の例
int a[4]; // ①
a[1] = 1; // ②
printf("添え字:a[1] = %d\n", a[1]);
a[1] = 0;
*(a + 1) = 1; // ②
printf("ポインタ:a[1] = %d\n", a[1]);
結果:
添え字:a[1] = 1
ポインタ:a[1] = 1