Version 4.10
アドレスとポインタ
「今日はポインタってものを紹介します」
『ええっ、ポインタ!?』
「ええって、知ってるの?」
『今、ちまたで噂になってるよぉ、ポインタは難しいって!』
「まー昔から言われてるけど、難しいのは〈配列〉にからんだ部分。ポイン
タそのものはかなり簡単だから安心していいよ」
『なーんだ、よかった』
「さてこの関数から」
void Address()
{
int i = 100;
TRACE( "%X\n", &i );
}
『んーと、これって i のアドレスを表示してるだけだよね』
「そう、変数の前に & を付けると、変数のアドレスになるんだよね。今回
はまずこの〈アドレス〉から。この例だとアドレスを直接出力してるけど、
そうせずに、代わりに変数に入れたい時にはどうすればいい?」
『フツーに int 型変数に入れればいいんじゃないの?』
void AddressToInt()
{
int i = 100;
int i2 = &i; // エラー。
}
『ええっ、ダメなの!?』
「エラーを見てみようか」
error C2440: 'initializing' :
'int *' から 'int' に変換することはできません。
この変換には reinterpret_cast, C スタイル キャスト
または関数スタイルのキャストが必要です。
「つまり、アドレスっていうのは単なる整数値じゃないってこと」
『……もしかして int * って型なの?』
「そういうこと。アドレスって、変数と密接に関わってるから、特殊なこと
ができないとまずいわけ」
『まずいの?』
「まずいの。というわけで、整数とは別の変数、その名も〈ポインタ〉に入
れる必要があります」
void UsePointer()
{
int i = 100;
int *pi = &i; // これがポインタ。
TRACE( "%X, %X\n", &i, pi ); // 64F530, 64F530
}
『おー、 int * ってのがポインタの型?』
「そう。ある型のポインタは、その型に * を付けたのが〈ポインタ型〉に
なります。ポインタも型のひとつだから」
『変数が作れる、それが pi なんだ。で、その中に i のアドレスが入る!』
「アドレスを出力するときに i は & を付けなきゃいけないけど pi の方は
付けなくていい、ってとこがポイントね」
『そりゃ pi にはアドレスそのもの、 64F530 とかが入ってるんだもんね』
「そう! そこ、すごーく大事だから」
『そんな大事?』
「そう。結局は数字入ってるだけってのがね。たとえばこの例」
void ShowPointerAddress()
{
int i = 100;
int *pi = &i;
TRACE( "%X, %X\n", &i, &pi ); // 64F530, 64F52C
}
『あれ? なんで pi に & 付けてるの? いらないんでしょ?』
「入ってるアドレスを表示するならね。ここでは pi って変数のアドレスを
表示してるから」
『??』
「 pi だって変数なんだから、メモリ上に置かれてるし」
『アドレスだってある、それを表示してるのね。なんかややこしい……』
「で、いつも通り止めて、今回はあとの 64F52C のアドレスを見て」
『 pi の中身を見るんだね。ほいっ』
0064F52C 30 F5 64 00 64 00 00 00 8C F5 64 00 42 2E 40 00 2C F8 64 00
『えっと、どうなってるのかな』
「ちなみにポインタのサイズは、アドレスの型に関係なく4バイトだから」
『あーそうだよね、メモリウィンドウのとこって 0x00000000 ってなってて、
16進数8桁って4バイトだからそうかなーとか』
「お”! なんかすごい……」
『そ?』
「ちなみにこの4バイトっていうのも、ウィンドウズが32ビットっていう
のと関係あるから」
『ポインタのサイズ= int のサイズ?』
「そういうことかな。ま、とりあえずは4バイトって考えておけば大丈夫で
しょ」
『で、4バイトってことは 30 F5 64 00 まで、 pi の中に入ってるってこ
とだよねこれは……』
「リトルエンディアンだから?」
『逆にして最初の 00 取って 64F530 ……あ! i のアドレスだ!』
「そゆこと。つまり、アドレスって値がポインタに入ってるってこと」
『ポインタも変数だから、こういうとこは同じなんだね』
「そう! 何度も言うけど、ポインタにはアドレスっていう数字が入ってる、
ってことすごく大事だから」
『そのイメージを忘れずに?』
「そういうこと。さて次。こっからちょっと難しくなるから覚悟してねー」
『はーい。ま、楽勝っしょ』
「じゃ、この例を見て」
void ReferencePointer()
{
int i = 100;
int *pi = &i;
TRACE( "%d\n", *pi ); // 100
}
『あれ、さっきと2行目まで同じだよね。最後の1行がちょっと違うけど。
ん、結果が 100 ? アドレスなら 64F530 とかなんない?』
「違うところは、まず %d で出力してるとこ」
『10進なんだ』
「そして *pi 、つまりアスタリスクが付いてるってとこ」
『え”、もしかして掛け算してるの!?』
「違う違う。 * はポインタと組み合わせて使うと〈ポインタに入っている
アドレスが指し示す変数の値〉を返すの」
『???』
「 pi のアドレスはどの変数の?」
『上の i のだよ。これが〈ポインタに入っているアドレスが指し示す変数〉
なんだから、 *pi はこの変数の値……あ、だから 100 なんだ!』
「そういうこと」
『うわっ、なんか混乱!!』
「つまり」
pi : アドレス
*pi : 指し示す変数
「ってことだね」
『うーん、つまりポインタって、普段はアドレスが入ってるんだけど、 *
を付けると変数に早変わり! ってことなんだ』
「そういうこと。いい!? ぜーったいに、普段はアドレスが入ってるって
こと忘れないでね」
『う、うん』
「普段はアドレスが入ってて、 * を付けたときだけ、指し示す変数になる、
ってこと」
『つまりアドレスがメイン、指し示す変数は一時的ってことね』
「その辺を焼き付けるためにいろいろ試してみます。まず次の関数」
void MovePointer()
{
int i = 100;
int *pi = &i;
TRACE( "%X\n", pi ); // 64F530
++pi;
TRACE( "%X, %X\n", pi, *pi ); // ここにブレークポイント。
}
『ん!? なんかポインタで足し算してる……』
「関数内5行目にブレークポイントセットして実行!」
『はいっ! 止めたらやっぱメモリ見るの?』
「そ、メモリウィンドウ開いて、3行目で出たアドレスを見てみて」
『うん、見たけど』
「そこの1行、こうなってるでしょ」
0064F530 64 00 00 00 8C F5 64 00 D2 31 40 00 2C F8 64 00 C0 01 52 00
『えーっと、最初の4マス、 64 00 00 00 が i の中身だよね。リトルエン
ディアンだから左の方が小さい桁で、 0x64 って 100 だから』
「そうそう。それを確認したら1行進めて」
『中カッコを飛び越すボタンっ!』
64F534, 64F58C
『って出力されたよ。えっと、ひとつめは pi の中のアドレスだよね。あれ?』
「3行目で pi の中のアドレスは 64F530 ってなってるでしょ」
『で、4行目でそのアドレスに1足してるんだよね。ってことは 64F531 に
なるはずなのに』
「結果は 64F534 、つまり4つ増えてます。なぜでしょー」
『……あてずっぽーで言うけど、 int が4バイトだから?』
「ピンポーン! 実は、ポインタへの足し算引き算は、ただの整数の計算に
はならないんです」
『つまり、ポインタの型のサイズだけ増えたり減ったりするってこと?』
「そういうこと。もしこれが char なら1ずつ増えるし、 short なら」
『2ずつ。でもなんでこうなってるの?』
「これは〈隣の変数を見る〉ため。もうひとつの出力を見て」
『 64F58C のこと?』
「これは ++pi 、つまり 64F534 の場所にある値のこと。このアドレスにあ
る値って?」
『えっと、一番左の 64 になってるマスが 64F530 だよね。 4 多いってこ
とは4マス右だから…… 8C ってなってるマスかな?』
「 int は4バイトサイズだから、そっから4マスは」
『 8C F5 64 00 で、リトルエンディアンだから逆順にして 00 を取ると
64F58C ……ああっ、これが出力されてるんだ!』
「そゆこと。ってゆーか、単純に〈ひとつ先のを表示した〉ってふうに考え
た方がいいかな」
『どゆこと?』
「 ++pi で〈ひとつ隣に移動した〉ってこと。さっきのを分かりやすく4マ
スごとに分けて書くと……」
64 00 00 00
8C F5 64 00
D2 31 40 00
2C F8 64 00
C0 01 52 00
「ってなるでしょ。こうやって、メモリ上に〈4バイトサイズのデータが並
んでいる〉ってみなすわけ」
『つまり ++ は、アドレス値を増やすってことじゃなくて、ひとつ先に進む
って考えた方がいいのかな』
「そう! 〈ポインタを進める〉って考えたほうがいいね。確認のためにも
うひとつ例を見ておこうか」
void MovePointer_short()
{
short sh = 100;
short *psh = &sh;
TRACE( "%X\n", psh ); // 64F530
psh -= 1;
TRACE( "%X, %X\n", psh, *psh ); // ここにブレークポイント。
}
『えっと、、変数が short 型になって、ポインタも short 型。あと……あ、
ポインタから1引いてる!』
「ってことはアドレスはどうなるってこと?」
『 short は2バイトサイズだから、アドレスは2少なくなるんじゃないか
な』
「では試してみましょう。出力結果はこんな感じ」
64F530
64F52E, 64
『 64F52E に 2 を足すと 64F530 だもんね、予想通り! で、メモリはっ
と』
「ひとつ前だから、ひとつ前の行から見てみると……」
0064F51C CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC 2E F5 64 00
0064F530 64 00 CC CC 8C F5 64 00 82 32 40 00 2C F8 64 00 C0 01 52 00
『左下の 64 00 が sh の中身でしょ。その……2マス前だから、右上の
64 00 』
「あ、結果偶然同じになっちゃったね。ま、そういうこと。こんなふうに戻
るってこともできます」
『まとめっ! アドレスを入れる変数、それがポインタ。 * を付けると、
そのアドレスが指す変数を見れる、ってことと、足し算引き算をすると、バ
イト数の分だけ増えちゃう』
「ぜーったい忘れちゃいけないのは?」
『ポインタにはアドレスが入ってる! だよね』
「そゆこと。それだけはぜーったいに、忘れちゃダメだよ」
『はい!』
/*
Preview Next Story!
*/
『ちょっとややこしいとこもあるけど、そんな難しくないよね』
「そんなこと言っていいのかな〜?」
『う”、いいもん、あたしはがんばるもん!』
「その調子その調子!」
『というわけで次回』
< Version 4.11 ポインタとキャスト >
「につづく!」
『う”、またポインタですか』
「難しくないんでしょ?」
『そ、そうよっ、がんばるんだから!』