#pragma twice

KAB-studio > プログラミング > #pragma twice > 115 Version 6.15 バグをコンパイルタイムエラーで防ぐ!

#pragma twice 115 Version 6.15 バグをコンパイルタイムエラーで防ぐ!

前のページへ 表紙・目次へ 次のページへ

 Version 6.15
バグをコンパイルタイムエラーで防ぐ!

ここまで演算子関係を中心にありがちなバグを紹介してきたけど、その最
大の理由は〈見つかりにくい〉から
確かに、前回の普通のオーバーフローとか、エラーになんないよね
そう。演算子を使った処理は、エラーが出にくかったり、なんでもでき
ちゃったり、関数を呼ぶわけじゃないからブレークポイントのセットとかで
きなかったり、と色々問題が起きやすいんです
そういうのの対処法ってことで、起きやすいバグの例ってのを見てきたん
だよね
そうなんだけど、でもそれだけじゃダメ。今までの例を知ってても、役立
つのは〈あれ、おかしい、どこが問題?〉って場面だけ
? じゃー役立たない場面って?
〈あれ、おかしい〉にならない場合
あ、そういえば、問題が出てこなかったら、バグが見つからないから探し
ようがないってゆーか、探すことすらないんだもんね
だから、今回からは〈いかにバグを発見するか〉について紹介していきま
す。ただ……
ただ?
ここからはちょっと大変。バグが見つかりやすいように常にプログラムを
組んでいくことになるから、コードを書く手間が増えるんだよね
病気になる前に健康食品毎日食べるようなもんね
それに、実際にバグにはならない状況でも〈バグがある〉って状況になっ
てうまくいかないとかあるんだよね
うまくいかないって?
たとえばコンパイルが通らないとか
げ! それってかなり大変じゃない?
今回は、その大変な方法について教えます
なんか大変そう……
で、その今回教えることは、バグをコンパイルエラーで防ぐ方法
バグをコンパイルエラーで?
そう。典型的な例が、 Ver 6.12 ( No.112 ) で紹介した if の例

void CDebugDlg::OnButton1() 
{
    int i = 0;
    if( 0 = i )
    {
        TRACE( "Hit!\n" );
    }
}

そうそう、 == を = って書いちゃったときに、こうしとけばコンパイル
エラーになるんだよね
こういうふうに、色々なバグの原因をコンパイルエラーとして検出するこ
とができるわけ
できるといいことって?
さっき言ったでしょ、ランタイムエラーとかは、エラーが見つからなきゃ
どうしようもないって
それに対してコンパイルエラーは……確実にエラーって出てくる!
そゆこと。バグは、バグの原因になってる部分が実行されないと見つから
ないし、実行されても、変数の中身とかの状況でバグが出なかったり
バグが出てても気付かないこともあるよね
そうそう。コンパイルエラーなら、すべてのコードがチェックされるし、
変数の場合分けとかと無縁だから確実なんだよね
でも、そういう変数の中身とかと無縁ってなんか不思議
そうだね、たとえば if( i == 0 ) と if( i = 0 ) を比較すると、実は
i が 0 以外の時は結果が一致するんだよね
げ、そういえばそうだ。 if( i == 0 ) は i が 0 の時だけ当たって、そ
れ以外は全部当たんない。 if( i = 0 ) は、 i が 0 になって、その 0 が
if にかかるから、絶対に当たんない。結果が違うのは 0 の時だけね
だから、ものすごく変な言い方をすると、 i が 0 以外の時は、バグじゃ
ない、って言えちゃうわけ。結果的にはね
……でもやっぱバグなんでしょ?
もちろん。どんなにその可能性が低くても、違う結果が出る可能性がある
のならそれはバグ。そこでコンパイルエラーの出番
 if( 0 == i ) は普通に動くけど、 if( 0 = i ) はコンパイルエラー
こうすることで、確率的に発生するバグを、事前に見つけだして直すこと
ができるわけです
 0 以外の時だろうがなんだろうが、コンパイルに通らないんだもんね
そう。また変なこと言うけど、こうするってことは、 0 以外の〈正しい
結果〉が出ている状況も、間違いだって否定していることになるんだよね
??
実行結果だけ見れば、 0 以外に関しては if( i == 0 ) と if( i = 0 ) 
は同じだったでしょ
そだね
でも重要なのは結果じゃない、ってこと。プログラマーが望むのは 
if( i == 0 ) という処理であって、その結果じゃないわけ
つまり、 0 以外かとかそうじゃないかとか関係なく、 if( i == 0 ) 
ってのをプログラマーが考えてたんだから、そういうコードじゃないとダメ
ってこと、かな?
そういうこと! 結果が同じだろうが似ていようが関係なくて、プログラ
マーが望むコードになってるかどうかが重要なわけ
だから、望むコードじゃないとコンパイルエラーになる if( 0 == i ) の
書き方がある、ってことね
ただ……
ただ?
たとえば元々あるプログラムの if( i == 0 ) を if( 0 == i ) の書き方
に変えていって、コンパイルエラーが出た部分を直していったら、想像と違
う結果が出なくなった、とかってこともあるんだよね
……それって、 if( i = 0 ) で常に当たらない、ってのが偶然うまくい
くようになってたってこと?
そういうこと。こういう場面に出遭って、原因が分からなかったりすると
辛いんだよね
でもそれって変じゃん! 絶対にヘン! 間違ってたのが運良くうまく
いってるだけじゃない
そうなんだけど……、何十時間チェックしても原因が見つからない、電車
も終わってもう帰れない、寝てもいないしごはんも食べてない、もういっそ
のこと元に戻そうか、〈うまく動いているプログラムには手を入れるな〉っ
て格言もあるし……
うわーぐろぐろもーどだ
とは言っても、それはその場しのぎ。後で少し手を入れる時とかにまた地
獄見るのは目に見えてるから
結果が同じならいい、ってことはないのね
やっぱり、プログラマーがどうしてそうなってるのか気付いてないとダ
メ。結果は同じでも、コードの書き方とかが違うと後で手を入れた時に想像
と違ってきちゃうから
そのためにも、どんなに大変でもちゃんとしとかないとダメってことね
そゆこと。プログラマーが想像した通りのプログラムになっているよう
チェックする、プログラムミスでそうなってなかったらコンパイルエラーに
なる
そういう書き方が、〈確率的に発生するバグ〉を防ぐことができるってこ
とね。あれ? でも、水希ちゃん、今の  if( 0 == i ) は使わないって
言ってなかったっけ
うん、そうなんだよね。 == か = かは、見た目にすぐ分かる間違いだか
ら、コード眺めるだけで見つかるんだよね。だから見やすさ優先かな
見た目にすぐ分からない間違いってあるの?
もちろん。たとえばキャスト。まずはやっちゃいけない例から

void GetInt( int *p_piRet )
{
    *p_piRet = 100;
}

って関数がある時に

void CDebugDlg::OnButton1() 
{
    const int i = 1;
    GetInt( (int *)( &i ) );
    TRACE( "%d\n", i );
    // 1
}

これって、いわゆる const を剥がすってやつ?
そう。 const なはずの定数値を無理矢理変数にして値を格納しようとし
てます。それに実際には剥がせてないし
ホント! 1 のままだ
こういう単純な定数値の時には、下の TRACE() の i が直接 1 に置き換
わるから、途中で中身が変えられても実際には変わらないんだよね
なんだか #define みたい
まー、 const int は #define の代わりに用意されたっていうのもあるか
ら、こういう機能になってるのかも
でもさ、こういうのって普通しないでしょ
どうかなー。新しく使う関数とかで意味不明な型が引数になってたりする
と、ついこういうことしちゃうと思うよ
でもどう typedef されてるかとか調べれば大丈夫だよね
もちろん。重要なのはそこ、つまり無理なキャストはしちゃダメってこ
と。ちゃんと型が分かってれば、キャストはまずしなくていいし
そなの?
ほとんどね。キャストせずに渡せてるのは正しいプログラムの証拠、か
な。あ、でも WPARAM みたくキャストが必要な時もあるけど
メッセージ使う時の話ね
 Ver 5.27 ( No.092 )でやったね。こういう時は仕方ないから、せめて
reinterpret_cast とかを使いましょう
これ、やっぱ使うといいんだ
キャストの目的が決まってるからね。さっきの const 剥がすのにこの
reinterpret_cast は使えないでしょ
うん、コンパイルエラーになるね。そっか、これもコンパイルエラーでバ
グを防ぐことになるんだ
そゆこと。ま、とにかくキャストはできるだけしない、ってことが重要。
これもコンパイルエラーがきついだろうけどね
まずは型を調べましょう、ってことね
あと今のにちょっと関わるけど、 const も重要
 const ってさっきの定数値とかだよね
定数値もそうだけど、それよりもポインタや参照と一緒に使う時の方が重
要かな
 Ver 4.12 ( No.062 ) でやったのだよね
そう、なんだけど、ここで深く突っ込んじゃうとちょっと難しい話になっ
ちゃうからこれは次回。ここで重要なのは〈気に掛ける範囲〉の話
範囲?
 const は置いといて、変数のスコープは憶えてるよね
変数が使える範囲でしょ。たとえば関数の中で作ったら関数の中でしか使
えない、とか
そうそれ。たとえばこういう例

void CDebugDlg::OnButton1() 
{
    int i1 = 0;
    TRACE( "%d\n", i1 );
    // 0
    i1 = 100;
    int i2 = 200;
    TRACE( "%d, %d\n", i1, i2 );
    // 100, 200
}

なんか、すんごーく普通の例に見えるんだけど
そう、ものすごく初歩的な例。ここで重要なのは、変数の使える範囲
両方とも関数の中でしか作ってないから、関数の中でしか使えないね
それだけじゃないよ。 i2 が作られるより上の行じゃ i2 は使えないで
しょ
当たり前じゃん。あ、これが〈範囲〉ってこと?
そう、これが範囲。こうやって後の方で作ることで、 i2 を事前に使うと
コンパイルエラーになるようにすることができるんです
コンパイルエラーになんない例って?
早めに i2 を作って、誤って書き換えちゃったとか
なるほど
でもこうすれば、書き換えたらコンパイルエラー。こういうふうに、変数
を後の方で作れば、その変数に対して〈気にかけなきゃいけない範囲〉が
ぐっと狭まるでしょ
〈気にかけなきゃいけない範囲〉ってなんかヘン
でも、安全なプログラミングにはそこが重要なんです。というわけで次回
に続く!

/*
    Preview Next Story!
*/
バグってやーね、出たりでなかったり
でもお金出してもらって、運が悪かったね、じゃまずいでしょ
お金出してもらってなかったら?
……これはここだけの話だけど……
うんうん
さっき言ったようにバグを見つけるためには手間がかかるから……
 reinterpret_cast とか書くの面倒だもんね
これをサービスと考えるか、必要なコストと考えるか……
こういうのに気を付けるってことがそれなりに大変ってことね
というわけで次回
< Version 6.16 気に掛ける範囲を狭める! >
につづく!
フリーのアプリにそれだけのものを期待するのは酷でしょ
 Linux とかも?
う”……
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。