Version 4.13
寿命とポインタ
「前回は const 型へのポインタっていうのを見てみました」
『定数ってものを使いたいからそーゆー仕組みがある、とかって話だよね』
「そうそう。で、今回はそれと関係しつつちょっと違う話」
『う”、また憶えることが……』
「まずはこの関数から」
void LocalVariable()
{
int i = 0;
TRACE( "%X\n", &i );
}
// 下の関数を呼びだしてください。
void CallLocalVariable()
{
LocalVariable(); // ここにブレークポイント。
}
『ん? 下の関数って、上の関数を呼んでるだけだよね』
「そ。とりあえず試してみて」
『はいっ。んと、 LocalVariable() 呼んでるところにブレークポイントを
セットして、ビルドして実行、ボタン押して、止まったよ』
「そしたらその関数の中に入って」
『【中カッコに入るボタン】で上の LocalVariable() にステップイン!』
「そしたら1行ずつ進めて」
『【中カッコを越えるボタン】を1回、2回』
「3回押したらいつものように」
『アウトプットの i のアドレスを見るんだよね。メモリウィンドウを開い
てっと』
0064F4DC 00 00 00 00 34 F5 64 00 CD 30 40 00 8C F5 64 00 C0 01 52 00
『うん、今は i が 0 だからそうだよね』
「そしたらもう1行進めて」
『関数から出て、 CallLocalVariable() に戻る……あ!』
0064F4DC 0D 38 40 00 34 F5 64 00 CD 30 40 00 8C F5 64 00 C0 01 52 00
『 i の中身、変わってる!! なんで?』
「これは、中カッコの中で作った変数は、中カッコから抜けると削除されちゃ
うから」
『中カッコ?』
「あ、それだと難しいか。もう少し絞って言うと、関数の中で作った変数は、
関数から抜けると削除されちゃうってこと」
『え、そうなの?』
「そう。こういう変数を【ローカル変数】って言って、ローカル変数は関数
の中でしか寿命がないわけ」
『関数抜けると死んじゃうんだ』
「まー表現的にはあってないんだけどね。ローカル変数が使ってたメモリ領
域は、他の変数が使うから」
『引っ越した家を他の人が使うって感じ?』
「そんな感じかな。これを踏まえて、次の〈間違った例〉を見てみましょう」
『間違った例?』
int *RetBadPointer()
{
int i = 0;
TRACE( "%X\n", &i ); // ここにブレークポイント。
return &i; // 警告が出ます。
}
// 下の関数を呼びだしてください。
void CallRetBadPointer()
{
int *pi = RetBadPointer();
*pi = 100;
}
『ビルドしたら警告出たよ』
warning C4172: ローカル変数またはテンポラリのアドレスを返します。
『あ、そういえば RetBadPointer() の中でローカル変数のアドレス返して
るね』
「そう、ポインタも戻り値にできるから。で、さっきみたくメモリ見てみて」
『んっと、アドレス見て、そこ表示させて』
0064F4D8 00 00 00 00 34 F5 64 00 8D 38 40 00 8C F5 64 00 C0 01 52 00
『で、 return で i のアドレスを返してっと。あ、確か返すとそれで関数
も終わるんだよね』
「そうそう」
『こうやって return して関数が終わると、関数がその変数になるんだから、
int *pi = &i; みたいな意味になるんだよね。……?』
「メモリはさっきみたいに」
0064F4D8 60 38 40 00 34 F5 64 00 8D 38 40 00 8C F5 64 00 C0 01 52 00
『……ねぇ、ってことはさ、 RetBadPointer() から返ってきたときにもう
i はなくなってるんでしょ?』
「そう」
『ってことは pi に入ってるアドレスって』
「 i の元あったアドレス、つまり今は何に使われてるか判らないアドレス
ってことだね」
『そそそそれってやばくない!?』
「ヤバイよ。そのあと pi を通して i の元あったとこに値入れてるけど、
これはやっちゃいけない例」
『そそそ、そうよね、ご老人がいることを確認して強盗に入ったらヤクザさ
んの家だったなんてシャレになんないじゃん!』
「それはどう転んでもシャレになんないよーな」
『じゃあ〈そこのドアを開けたらお化けが!!〉〈がちゃっ。いないやん〉
みたいなドリフチックなのは?』
「それだ! じゃなくてー」
『じゃなくて?』
「つまり重要なことは、関数の中で作ったローカル変数を、戻り値として返
しちゃダメ!! ってこと」
『しちゃいけないことができちゃうの?』
「だから警告出たでしょ」
『あ、そっか』
「でも、実行したときにエラーが出ないから、その点には注意」
『それってテストしてもわかんないってことだよね』
「そうそう」
『……ふと思ったんだけど、実行して問題ないんなら、いいんじゃない……
ってのはもちろんまずいって思うんだけどさ』
「そうそう」
『水希ちゃん流に言うなら〈手術中に1円玉落ちちゃった、まー問題ないか
ら取らないでいいや〉みたいな感じ?』
「なんで1円玉……」
『まーでも、なんかちゃんと動いてるっぽいからいーやん、みたいな感じは
ちょっとするよ』
「そう、〈分からないと〉みたいな部分があるからね。たとえば環境ホルモ
ンが出る製品を使うのとかって」
『そういうのって見えないから気にならないとかあっちゃうかもしんないね』
「でも密かに影響あったりするでしょ」
『そういうものってことなんだ。確かに、この例じゃ影響でなくても、おっ
きなプログラム組んだりして、長い間使ったりしたら、影響出るかもしんな
いんだもんね』
「だから重要なのは、仕組みをちゃんと理解してて、なんでいけないのかっ
てことをちゃんと分かってるってことが大事」
『あたし達は整備士。車にはガソリン、じゃなくて、なんでガソリンじゃな
きゃいけないのか、が分かってないといけないってことね』
「そゆこと」
『あっそうそう、でさ、結局ローカル変数ってなくなっちゃうんだから、戻
り値としてポインタを返すのってダメってことだよね』
「そうとは限らないよ。外の変数のポインタを返すことはできるから」
int *RetPointer( int *p_pi )
{
return p_pi;
}
// 下の関数を呼びだしてください。
void CallRetPointer()
{
int i = 100;
int *pi = RetPointer( &i );
TRACE( "%d\n", *pi ); // 100
}
『うわ、 RetPointer() って面白い! ポインタ受け取って、すぐ返しちゃ
うんだ』
「ま、アドレスを値って考えて、それを受け取って返すだけ、ってことだね」
『通ってるアドレスは、他の関数の変数なんだ。あ、これは大丈夫なんだね』
「そう、ローカル変数は〈関数が終了したら消える〉から、終了しない限り
なくならないわけ」
『で、通ってきたアドレスを受け取って表示したら i と同じ、ね』
「そういうこと。あと、ちょっと難しいけど〈変数を動的に作成する〉って
こともできて、そのときにはポインタを返せるから」
『どゆこと?』
「こういうこと」
int *RetNewPointer()
{
return new int;
}
void UseRetNewPointer()
{
int *pi = RetNewPointer();
*pi = 100;
TRACE( "%d\n", *pi ); // 100
delete pi;
}
『うわあああっ! なんかいきなりむずかしチック!』
「うん、実際そうだと思うからちゃんとした解説は今度ってことにするけど、
とりあえずどう?」
『 new と delete っていうのが初見……』
「 new は、特殊な方法で変数を作るためのもの。こうやって作るとローカ
ル変数とは違う別のタイプの変数を作ることができて」
『それは関数から抜けてもなくならない?』
「そういうこと。だから関数の外でも平気で使えるんだけど、逆に使い終わ
ったら削除しなきゃいけないっていうデメリットがあるわけ」
『それが delete ?』
「そうそう。こういう方法なら、ポインタを返すことができるでしょ」
『うん、ってゆーか聞けば簡単、かな』
「そうでもないよー。たとえば、 delete する前にアドレスが消えちゃった
ら?」
『削除できない……』
「 delete したのにアドレスが残ってて、そこにアクセスしちゃうとか」
『あわわわわ……』
「ってことで、ただ使うのは楽でも、使いこなすのにはいろんなテクニック
が必要だから」
『確かにねー、ローカル変数ならそういう心配はいらないんだもんね』
「ま、色々注意しなきゃいけないのは、普通にポインタ使う時も同じ」
『つまり〈あ、ポインタを返しちゃいけないんだ〉とかってことを常に気を
付けておく必要があるってこと?』
「そういうこと」
/*
Preview Next Story!
*/
『ポインタって注意必要怪我一生って感じ』
「それはどんなに熟練してもそう」
『げ、これからもずっと心配し続けなきゃいけないの?』
「そのために、安全性を高めるテクニックがあるんだな」
『というわけで次回』
< Version 4.14 引数に使うポインタ、そして参照 >
「につづく!」
『テクニック使ったら心配事増えたりして』
「ぎくっ……」