第 4 回コールスタックの活用 しかし、そこが本当にこのプログラムの中間地点なのだろうか ? hoge-function の実行 は、 1 回目に呼び出されたときだけ 10 秒かかって、 2 回目以降は 1 秒しかかからないかもし れない。 このとき、実行時間で見れば、 5 回目に hoge ー function を呼び出した直後というのは、中 間地点から大きくかけ離れている ( 図 1 ) 。 マ図 1 1 回目の呼び出しから復帰までに時間がかかる 回目 9 回目 8 回目 7 回目 6 回目 5 回目 4 回目 3 回目 2 回目 1 回目 実行時間 「プログラムをちょうど ( 時間的に ) 中間の地点で止めるというのは容易ならざる ことなんだな」 それは、つまり .. 「ケンイチ君。朝ですよー」 ひなた先生に起こされるケンイチ。 「もにゆもにゆ」 ケンイチは自分が机にうつ伏せになったまま眠っていたことに気付いた。 「よかった。部屋に入ってきたらケンイチ君、倒れてるんだもん。ビックリしたよ」 「す、、すみません」 「ケンイチ君もおねむ ? 」 61
0 書籍イヒに当たっての注意事項 本書は、技術評論社発行の『 Software Design 』に掲載された記事を必要 に応じて加筆 / 修正したものと新規書き下ろし記事を加えて再編集した書籍 です。再掲載記事の中には、動作検証を行った OS のバージョンなどが初掲 載当時の環境である場合がありますので、ご注意ください。 第 1 回 デバッグにおける不確定性原理 Software Design 2005 年 5 月号 第 2 回 バグの発生点と発現点 Software Design 2005 年 6 月号 ニ分法によるバグ発生地点の絞り込み 第 3 回 Software Design 2005 年 7 月号 第 4 回 コールスタックの活用 Software Design 2005 年 8 月号 第 5 回 関数の hooking Software Design 2005 年 9 月号 第 6 回 Software Design 2005 年 1 0 月号 SparseMatrix による logging 第 7 回 proxy object によるバグの再現 Software Design 2005 年 1 1 月号 第 8 回 ケアレスミスはなぜ起きるのか ? Software Design 2005 年 1 2 月号 第 9 回 プログラミングのイディオム ( 前編 ) Software Design 2006 年 1 月号 第 1 0 回 プログラミングのイディオム ( 後編 ) Software Design 2006 年 2 月号 第 1 1 回 例外処理の考え方 Software Design 2006 年 4 月号 第 1 2 回 変数の命名規則 Software Design 2006 年 5 月号 第 1 3 回 関数の返り値はどうあるべきか Software Design 2006 年 6 月号 第 14 回 ホインタと参照との関係 Software Design 2006 年 7 月号 最終回 バグを憎んで人を憎まず Software Design 2006 年 8 月号 掲載号
0 。 00 , 、 0 , " デパッグな 5 も 1 ウくなるテクニック これがバグっているって、そりや、ここからすべてのハンドラが呼び出されるんだから、 ここら付近がバグっていることには違いない。だけど、それは、アフリカ砂漠のどこかに 宝の地図を隠したというのに等しい。そんな宝探しは宝探しとして成立しないと思う。 ケンイチは、 5 分で終わるどころか 5 時間やそこらでは終わらないと思った。 ケンイチは、 dispatcher 関数を調べた。 ▽リスト 1 dispatcher 関数 void dispatcher(int messagel ′ int message2){ switch(messagel){ switch 文以下には case が 3 万行ぐらい並んでいた。ケンイチは軽いめまいがした。 0 「こりやひどい」 ケンイチは今にも泣き出しそうだ。ケンイチは、しばらく実行させて様子を見ることに した。しかし、 dispatcher というだけあって、実行中に何百回、何千回と呼び出されてい るようだった。 「ロギングするか」 ケンイチは、バラメータを printf してみることにした。 マリスト 2 p ⅱ ntf でロギング void dispatcher(int messagel ′ int message2){ printf("%d ′ %d\n" ′ messagel ′ message2); 流れていく口グは何百行どころで済まない。ケンイチは、 100 回に 1 回だけログを書き出 すことにした。 マリスト 3 100 回に 1 回ログを書き出す static int log_count=O; if (( + + log_count %IOO)==O) printf("%d ′ %d\n" ′ messagel ′ message2); ケンイチの期待とは裏腹に、流れていく口グの量は膨大であった。ケンイチは 100 回に 1 回のところを 10,000 回に 1 回にしてみたが、結果はあまり変わらなかった。ケンイチは 100 万回に 1 回にしてみた。そうすると、ログが流れる速度はゆっくりになった。 0 116
第 9 回 プログラミングのイディオム ( 前編 ) マリスト 6 ダウンカウントによる n 回繰り返し for(int i=n;i>O; do_something(i); 「ループ変数をダウンカウントしてるんですね。このプログラム、一目しただけで は n 回のループだとはわかりにくいです。ループ条件が i > O となっていますが、書き 慣れていないとここを間違えて i > = O と書いたりしそうです。あるいは、 i>=O と書 いてあったりしても、それが誤りであることに気付かないかも知れないです」 「どうすれば、 i > O と i > = O を間違えずに済むかしら ? 」 「境界テストを頭の中でしてやると良いと思います。具体的には、 n が O と 1 のとき に正しくループするかどうかを考えるのです。 n が O だと O 回ループ、すなわち、ル ープせずに fo 「を抜けなければなりません。 fo 「の条件式が fi>=O 』だと、 1 回ルー プしてしまうので、誤りだとわかります。 また、同様に、 n が 1 だと 1 回のループ、すなわち、 1 回だけ do_something を実 行して抜ける。このことを頭のなかで確認してやります」 「なるほど。じゃあ、リスト 1 のようなループでは、そういう確認を頭の中ではしな いの ? 」 「しないですね。ああいうのは、見慣れているのでそこまで確認しなくとも n 回まわ るループであることはわかります。熟練したプログラマにとっては、 i の取りうる値す ら意識にのほってないと思いますよ。きっと、バターンとして認識していますから」 いちもく . 「うんうん」 「あと 1 つ気付いたのですが、後者のループは、 i が n から 1 までのカウントになって います。冒頭に出てきたループ ( O から n - 1 までのカウント ) と対称にしようと思 うと i は、 n-l から O までのカウントにしないといけないと思います。つまり、こう です」 ▽リスト 7 ダウンカウントによる n 回繰り返し for(int i = n ー 1 戸 > = 0 ーー i ) do—something(); 165
第 9 回プログラミングのイディオム ( 前編 ) 「 1 0 の部分が n に置き換わるだけですね」 for(int i = 0 冫 i く n 冫 + + i ) d0 something(); 「このとき、ループ変数 i の取りうる値は ? 」 「ループの中では、 i は O から n - 1 ですね。で、ループを抜けるとき i が n になる ? 」 「うん、正解 ! 」 「でもふだんあんまりそういうの意識しないですね。もう、 n 回まわるループがそう だと思ってるので」 「うん、そうよね」 「そもそも、 i が O から n - 1 までで合計 n 回まわっているのですけれど、それがバッと 出てこないと思うんです。最初、この表現を見たときは、『あれ ? n - 1 回しか回って ないんじゃないの ? 』とか不安になりました」 「そのときは、どうやって n 回まわるループであることを確かめたの ? 」 「 n に小さな数字を入れて成り立つかどうかを試してみたことを覚えています。 たとえば n に 1 を入れて考えると『 i が O から n - 1 まで』っていうのは『 i が O から O ま で』っていう意味になって、ああ、ループ内では i は O を取って、 1 回は実行されて いるんだな、とわかります。 n に 2 を入れて考えると『 i が O から n - 1 まで』っていうのは『 i が O から 1 まで』っていう 意味になって、ループ内では i が O と 1 を取って、 2 回まわるんだなってわかります」 「うんうん。小さな数字を入れて試してみるのは良い方法ね ! ところで、 fi<n 』と いうのは『 i!=n 』ではダメなの ? 「 n 回まわるループにおいてはその 2 つの差異はありませんが、 '<' が 1 文字なのに 対して " ! = " は 2 文字ありますから文字のタイプ量で劣っています。また、『 + + i 』 が『 i + = 2 』のようになった場合に、 fi!=n 』だと正常にループが終了しなくなりま 163
第 2 回バグの発生点と発現点 「 pe 「 son と act をロギングするためにファイルに書き出そう」 ケンイチはさきほどの関数 ( リスト 1 ) に次のように追記した。 void gekogeko(Person person ′ Action act){ person. print(debug_out) ; act. print(debug_out); person. dOAct(act); 実行すると、ログファイルが生成された。プログラムは一向に応答せず、ハードディス クのガリガリという異音とともに、ログファイルは肥大化し続けているようだった。 「なんということだ。この関数は、何万回どころか、下手すると何億回も呼び出さ れているのかもしれない。凄い勢いでログが増える一方だ」 ケンイチは真っ青になりながらプログラムの実行を中断して、さらに次のように追記した。 VOid gekogeko(Person person ′ Action act){ static uint n = 0 ; if ( ( n 十十 % 1000 ) = = 0 ) { person. print(debug_out); act .print(debug_out); person. doAct(act); こうすれば、 1 , 000 回に 1 回しかログは出力されない。ケンイチの思惑では、ログの出力 回数が 1 / 1000 になれば、このプログラムはもう少しまともな動作をするはずだった。 確かにその思惑どおり、プログラムは“まともな動作”をした。先ほどのようにロギン グに時間がかかって CPU 負荷を 100 % にしてしまうこともなかった。ただ、実行して、し ばらくすると以前のようにバグが発現した。そのあとブレークさせて調べてみたが、ログ ファイルには同じことの羅列しか書き出されていなかった ( 図 4 ) 。 「これは困ったことだ。ロギングされているのは、 1 , OOO 回に 1 回だけだ。残りの 999 回は記録に残っていない。その 999 回すべてが、ここに記載されている 『 person = ケンイチ , act = 人を好きになる』という状態なのか、それとも違う内 容なのか、それすらわからない」
0 テパッグが ック 電 . ーくなるテクニ 。「ひなた、今夜は寝かさないムニャムニヤ・・・」 「な、なに言ってるのよ、ケンイチ君」 ひなた先生は顔が真っ赤だ。 : 「ひなた、今夜は最高の夜にムー ーヤムー 「こ、こらケンイチ君 ! 」 ひなた先生が教える 0 ひなた先生がケンイチのほっぺたをつねった。 0 「あっ。ひなた先生、おはようこざいます ! 今日もこ機嫌うるわしゅうこざいます」 「うるわしくないっ ! 」 プログラミングのイディオム 1 「 n 回まわる for ループ」の巻 0 「それでは気を取り直して・・・今日は、プログラミングでよく使うイディオムについて考 えてみます」 「はい。イディオムというのは熟語とかのことですか ? 」 0 「うん。よく使う、決まり文句みたいなの。たとえば、 C + + で 1 0 回の繰り返しはど うやって書くかな ? 」 0 「ええと、こうですかね ? 」 ケンイチは自分のノートにリスト 1 のように書いた。 v リスト 1 10 回繰り返し for(int i = 0 i く 10 冫 + + i ) do_something( ) 冫 0 「そうね ! これが IO 回ではなく、 n 回だと ? 」 162
第 4 回 ひなた先生が テ / ーッグが ルス くなるテクニック の活用 ケンイチが入社して 3 カ月が経った。仕事には慣れてきたが、知らないことはま だ山のようにあった。毎日が新しい知識との出会いであり、新しい知識を乾い た砂が水を吸収するがごとく自分のものにしていくケンイチであった。 毎週水曜日は観音菩薩の日 水曜はひなた先生の授業の日だ。ケンイチにとって、ひなた先生は砂漠のオアシス的な 存在であり、荒野に咲く一輪の花であり、観音菩薩そのものだった。ケンイチは、いつも より 30 分早く出社し、授業のある特別会議室に向かった。 しばらく後に繰り広げられる授業を想像するとケンイチは胸が躍った。 「よし。今日は予習しておこう。」 特別会議室に着いたケンイチは、さっそく授業で使っている大学ノートを開いた。そこ には前回の授業の内容で出てきたバグの発生点をニ分法で絞り込んでいくテクニックにつ いて走り書きがしてあった。 バグの発生点を絞り込むときに、一番効率が良いのは、ニ分法だろう。バグがありそう な範囲を半分ずつに絞っていくわけだから。理想的には、クイックソートのように log の オーダーでバグの発生点に近づくことができる。要するに、メチャクチャに収束が速い。 しかし、プログラムを「ニ分する」こと、つまり真っニつに分けるというのは一般的に 可能なのだろうか ? リスト 1 のような、何らかの関数を 10 回呼び出すプログラムがあるとする。 v リスト 1 10 回何か呼び出すプログラム for(int i = 0 く 10 冫 + + i ) hoge—function(); 「 10 回、 hoge-function を呼び出す」プログラムのその中間地点でブレークするとすれ ば、 5 回 hoge_ function を呼び出した直後 ( か 6 回目に hoge_function を呼び出す寸前 ) を 選ぶだろう。 0 60
第 1 回デバッグにおける不確定性原理 しかし、ケンイチの淡く切ないそれらの想いはすぐに打ち砕かれることになった。 最初、ケンイチは、ブレークポイントを仕掛ける場所に困った。まずプログラムの先頭 にブレークポイントを仕掛けてみた注 2 。 そこから 1 行ずつステップ実行して行った。そうすると、最初の for 文で 10 万回まわって 配列を初期化しているところにぶち当たった。 for(int i=O;i く max; + + i) b 〔 i ] true; この部分を 10 回ほどステップ実行させてみたが、 2 回ステップ実行するごとにループ変 数である i が 1 つずつ増加していくだけで、こんな退屈な作業を IO 万 X2 回もやらないとい bCjJ = false; / / 倍数を潰していく for(int j=i;j く max;J + =i) i f ( bC i ] ) for(int i=2;i く max; 十十 i) しかし、ケンイチを次の fo 「文が待ち構えていた。 みた注 3 。 10 万回の fo 「はあっという間に通り過ぎた。 仕方ないので、その次の for 文のところにブレークポイントを仕掛け、「続行」を選んで けないのかと思うとケンイチは気が遠くなった。 を行えば良い。 VisuaIStudio では、次の fo 「のところにマウスカーソルを置いて、マウスの右クリック→「次のステートメントまで実行」 注 3 ブレークポイント ( 続行 ) 「一時停止」した状態になる。 Visu studio ではビルドを終わらせたあと単に「ステップイン」か「ステッブオーバー」を行えばプログラムの先頭で 注 2 ブレークポイント ( 一時停止 ) こを抜けたところにブレークポイントを仕掛けて、そこまで「続行」してみた。仕掛け プ実行で 1 行ずつ追いかけたところで何も得られないとケンイチは思った。 に気が遠くなって気絶しそうになることになろうとは思ってもいなかった。これをステッ えているからだ。ケンイチはまたもや気が遠くなった。まさか入社して 20 分にしてこんな この fo 「文は手ごわい。なぜなら、 if の条件式が成立すると、さらに後段にも for が待ち構
第 6 回 第 7 回 第 8 回 第 9 回 6 Sparse Matrix による logging やばい ! ! 遅刻かも ? の巻 ケンイチのヒミップログラムの巻 . G00 図 e の Page 日 ank のしくみの巻 PageRank を行列計算で考えるの巻 ケンイチの昼休みの巻 噴火寸前の巻 . 5 分と 5 時間の巻 Sparse Mat 「 ix の実装の巻 ケンイチの講義ノート .. 104 .. 105 .. 105 .. 111 .. 113 .. 114 .. 115 .. 118 .. 122 ~ 123 104 今回のまとめ / Spa ℃ e Mat 「ⅸを使うロギングは本当に役に立つのか ? プログラマのお給料って凄いの巻 バグを絞り込むの巻 . プログラムを変形してバグの原因を絞り込むの巻 . 削ろうにも削れないの巻 早井先輩は健忘症の巻 ヘビー級オブジェクトの巻 . ケンイチの講義ノート 今回のまとめ proxy object によるバグの再現 ケアレスミスはなぜ起きるのか ? 対称性を意識しろ ! の巻 早井先輩は、最近ヘンの巻 再帰的プログラミングの巻 迷路から脱出せよの巻 一緒にお風呂の巻 . ケンイチの講義ノート .. 142 .. 14J 145 .. 152 .. 154 124 .. 124 .. 126 .. 128 .. 1J2 .. 134 .. 1J5 .. 141 142 .. 158 ~ 159 今回のまとめ / スキャンラインアルゴリズム / Visua 旧 tudi0 ミニテク ニック③ ( 識別子の宣言を表、インテリセンスの入力候補一覧を表示 ) ケンイチはうそっきの巻 . プログラミングのイディオム 1 「 n 回まわる fo 「ループ」の巻 プログラミングのイディオム ( 前編 ) .. 160 160 .. 162