ログイン
kid's world

バグ

バグ

ここで一息ついてお茶やお菓子でも食べながら,プログラム(実行ファイル)ができあがるまでの作業手順を見直してみよう。

(1)エディタを起動する

(2)エディタにC言語のプログラムを書く

(3)テキスト形式でファイルを保存(拡張子は「c」)

(4)保存したソースファイルをコンパイラでコンパイルする

以上の手順で,実行可能ファイルは作られる。この手順を見てみればわかるように,この作業はコンピュータと人間の共同作業である。ということは……何らかの誤りが,この作業の中には紛れ込む可能性があるということを意味している。

一般的に,プログラムが動かないとかプログラムが意図したとおりの結果を出さないということがときどき起こり,これを俗に「バグ(誤り)がある」などという。できればバグの紛れ込んだプログラムは作りたくない。使う人にとっても,バグの入ったプログラムなど使いたくはない。

ではバグはどのようにして紛れ込むのだろうか。まさか,コンピュータ自身が間違えて動かないということは(ないわけではないが)あまり考えられない。コンピュータは,かわいそうになるぐらいひたむきで正確である。文句も言わず,与えられた仕事を確実にこなしていく。

つまり,バグは先ほどの作業手順の中で「人間が担当する部分」において発生しているのである。

よって,それを知った今日からは,バグってコンピュータが止まったからといって,コンピュータを叩いてはならない。叩く対象はその原因を発生させたプログラマなのである。したがって,あなたが今後プログラマになって叩かれることがないようにするためにも,この機会にバグについて真剣に考えてみよう。

では,人間が担当する部分というのはどこであるかを考えてみよう。

(1)は人間がコンピュータに指示をするわけであるが,「エディタを起動せよ」とアイコンをクリックするだけなので,ここで人間の誤りの入り込む余地はない。もしエディタとゲームのアイコンを間違ったとしても,遊べて楽しいとか,時間がなくなって納期に間に合わないという弊害が出るだけであって,バグの入り込んだプログラムが作成されるわけではない。つまり(1)ではない。(3)や(4)も同様である。人間は指示を下すが,その指示は単純なものであり,コンピュータはその与えられた仕事を正確にこなすのである。よってバグが入り込む余地はないのである。

ということで,消去法でいくとバグは(2)の作業で発生しているということになる。プログラムを書くという作業は,コンピュータが受け身になって人間の打った文字を画面に表示している状態であり,コンピュータはそれがどういう意味を持っているのかをまったく考えていない。したがって,この作業は完璧に人間の思考によって行われているのである。

わざと間違える

なにごとにも,「訓練」が必要である。避難訓練がそうであるように,危機的な状況を仮想的に起こしてみて,その中でその危機的状況を脱するための術を身につけるのである。というわけで,わざと誤ったプログラムを作成してみて,それを修正しながら学習してみよう。

打ち忘れ

C言語のプログラムを書き始めたころによく起こすミスは,List 7のようなものである。どこが間違っているかわかるであろうか。

List 7: bug1.c

#include <stdio.h>

main()
{
  printf("バグってる\n")
}

このプログラムを実際にコンパイルしてみよう。LSI-Cでコンパイルしようとすると,コンパイルは中断してエラーが表示された(Fig. 2)。

Fig 2: bug1.cのコンパイル

C:\LSIC86\TMP>lcc bug1
bug1.c 6: syntax error near '}'

C:\LSIC86\TMP>

メッセージはコンパイラによって異なってくるが,ほとんどの場合はソースファイル名,行番号,エラーの内容などが表示される。LSI-CはFig. 2のエラーメッセージを通して,

bug1.cの6行目でエラーです。}の近くに文法的な誤りがあります。

という意味のことを伝えているのである。

6行目といわれたからといって,本当に6行目に間違いがあるとは限らない。「6行目でエラー」というよりは,「6行目までとりあえず読んでみてエラーだとわかった」という意味である。

つまり,6行目以前に問題があった可能性がある。答えはわかったであろうか?

そう,5行目の最後で「;」(セミコロン)が抜けている。前半部分で解説したように,C言語の文の最後には「;」が必ず必要なのである。それが欠落していたために,LSI-Cはエラーを報告しているのである。

打ち間違い

では,次の例である。やはりList 8には誤りがある。

List 8: bug2.c

#include <stdio.h>

main()
{
  pritnf("バグってる\n");
}

この誤りを含んだソースファイルもコンパイルしてみる。

Fig. 3: bug2.cのコンパイル

C:\LSIC86\TMP>lcc bug2
bug2.c 5: Warning: function 'pritnf' undefined -- assumed to be int
lld @link.i
bug2.obj(bug2): Undefined symbol: pritnf_

C:\LSIC86\TMP>

今度は2つのメッセージが表示された(Fig. 3)。まず1つ目はこんな意味である。

bug2.cの5行目で警告です。pritnf関数は定義されていません。intとして扱います。

「int」というのはここではまだわからなくてもいい。重要なことは,

pritnf関数なんて知らないよ

といっているということである。しかしLSI-Cは,文法的には間違っていなかったので,そういう名前の関数があるんだと思い込んで,とりあえずコンパイルを続行する。そして最終的に,

pritnf_というシンボルは未定義です

というエラーメッセージが表示される。最終的に実行可能ファイルを作ろうとしたが,「pritnf_」というのを知らないので実際にファイルを作れなかった,というわけである。今回はどこを直せばよいのだろうか。「pritnf関数なんて知らないよ」といっているのだから,この関数に問題があるようである。よく見るとつづりが違う。「printf」と打つべきところを「pritnf」と打ってしまっていたのである。

対応関係

では,次の例(List 9)はどうか。

List 9: bug3.c

#include <stdio.h>

main()
{
  printf("バグってる\n);
}

これもときどき起こす誤りである。さっそくコンパイルしてみよう。

今回の結果(Fig. 4)は,その誤りをずばり指摘してくれた。

Fig. 4: bug3.cのコンパイル

C:\LSIC86\TMP>lcc bug3.c
bug3.c 5: missing "
bug3.c 6: syntax error near '}'

C:\LSIC86\TMP>

bug3.cの5行目でエラーです。「"」がありません。

文字列は「"」で囲まなければならないという決まりがあるのだが,ここではそれを片方打ち忘れてしまったのである。この「"」(二重引用符)や「(」「)」(カッコ)などは対応関係がとれていなければならない記号である。

この片方を打ち忘れたり,おかしな位置に書いてしまうと場合によってはまったく的外れなエラーメッセージで訴えかけてくることもあるので注意したい。

コンパイルはできるが

次の例(List 10)には非常にわかりにくいバグが潜んでいる。

List 10: bug4.c

#include <stdio.h>

main()
{
  printf("%d\n", 10/3);
}

では,このコードもコンパイルする。

Fig. 5: bug4.cのコンパイルと実行

C:\LSIC86\TMP>lcc bug4
lld @link.i

C:\LSIC86\TMP>bug4
3

C:\LSIC86\TMP>

コンパイルはできてしまった(Fig. 5)。実行可能ファイルもしっかりと作られた。そこで実行してみる。画面には「3」という数字が現れた。

「どこにバグがあるんだ?」と思われた方は,ちょっと計算してみてほしい。本当に「10÷3」が「3」だろうか?

このプログラムは,「10÷3」の答えを表示するプログラムである。したがって,「3」などという誤った答えを表示してはならない。意図した動作がなされていないのだから,明らかにこれはバグである。

この例をみれば,

コンパイルができたからといって,バグが存在しないと思い込んではならない

ということがよくわかるであろう。わかりにくくて厄介なバグは,このような類のバグである。

では,これはどうすればちゃんとした答えを表示するようになるのだろうか。それは次回のお楽しみ,としておきたい。

プログラムが複雑になればなるほど,「コンパイルはできるけれども,うまく動かない……」というバグと戦う機会が増えてくる。実は,天才的なプログラマでない限り,とりあえずコンパイルできるコードを書き終わるまでの時間よりも,このような厄介なバグを取り除く「デバッグ」の作業に費やす時間のほうが多くなることがあるのである。

このようなバグは,まだプログラマのC言語に関する知識にあいまいな部分があるか,プログラマが想定していた処理の手順,すなわち「アルゴリズム」と呼ばれるものが誤っているために発生する場合がほとんどである。どうしてもバグの原因がわからない……という状態になった場合は,この2点から疑ってみてもらいたい。つまり自分の「知識」と「考え」にバグがないかを疑うのである。

おわりに

次回は,コンピュータの重要な部品である「メモリ」の話を織り交ぜながら,C言語の中でもっとも活躍する「変数」について解説をする。

この連載を読んでいる読者のみなさんから,「こう書いたほうがよい!」というようなご意見や,叱咤激励をいただけると筆者としてはこのうえない幸せである。ぜひぜひ,怒涛のごとく意見をお寄せいただきたい。


なぜ変数は必要か」へ進む

広告


©Toshio Koide 1996-2007.

目次

リンクについて

リンクは御自由にどうぞ。

メール

mail.gif

広告