第79章 Significance of Errors

 第77章で2次元配列の要素数を両方省略したときのコンパイルエラーを紹介しました。あの意味って何なんでしょうね? その他、いろいろなコンパイルエラーがありますが、一見無茶苦茶なエラーを吐いているように見えることはよくあります。今回は、こういったエラーの暗示するその意味について考えてみたいと思います。

 コンパイルエラーは使っているコンパイラによって変わってきます。一応ここで紹介するのはVC++のエラーです。ある程度は似ていると思いますが、もし極端に内容の異なる場合は独自に研究して下さい。


 では、今回の要点です。


 では、いってみましょう。


 さて、第77章のコンパイルエラーをもう一度見てみましょう。

エラーの原因
char result[][] = {
    "前者は後者より小さいです。",
    "両者は等しいです。",
    "前者は後者より大きいです。",
};
コンパイルエラー
error C2087: '<Unknown>' : 添字が不正です。
error C2117: '前者は後者より小さいです。' : 指定された配列には、初期化子が多すぎます。
error C2117: '両者は等しいです。' : 指定された配列には、初期化子が多すぎます。
error C2117: '前者は後者より大きいです。' : 指定された配列には、初期化子が多すぎます。

 先ず、エラー番号C2087の「<Unknown> : 添字が不正です」です。ヘルプを見ると、このエラーの原因は「複数の添字を持つ配列定義の最初の次元以外の次元の添字の値が指定されていません。」だそうです。ヘルプを見るとよく分かりますが、それにしても何とも分かりにくいエラーメッセージですね。大体 <Unknown> って何なのでしょうか?

 残念ながら <Unknown> というのが何かはよく分かりませんが、とりあえずこのエラーの原因はよく分かりました。

 さぁ、このコードのエラーはその部分だけのはずです。そのエラーメッセージもありました。では、残る3つのエラーメッセージは何者なのでしょうか?

 もちろん、2つ目の添え字に適当に64とでも入れてやれば、エラーメッセージは全てなくなります。他にエラーなど何もないのです。なのに3つも余分にエラーメッセージが出ているのです。何とも不思議ですね。

 しかし、こういうことはよくあることです。あるエラーが起こると、そこでプログラムの解析に異常が起きます。そこでコンパイラは「ある手段」をもってこのことに対処します。この「ある手段」によってエラーから適切に復帰されればいいのですが、必ずしもそうはなりません。すると、エラーの発生したところからプログラムの解析がおかしくなり、そのまま芋づる式に別のエラーを誘発してしまうわけです。そのエラーは、最早真面目に読む価値はないです。

 このように、何だか関連性のありそうな一連のエラーがあった場合、大抵は初めの数個が重要であるだけで、残りのエラーを一生懸命読む意味はないのです。

 例えば、ここでは添字のないカッコがあるということで、その添字のないカッコには1が入っていると仮定されます。これが「ある手段」です。すると result は実質 char 型の配列になります。そして、初期化子は { } で囲んでいるので、各要素毎に初期化を行うことになります。しかし、result の要素は char 型1つの配列つまり1バイトなので、これを文字列で初期化しようとしても初期化子が多すぎると言ってくるわけです。

 こういった表面的な意味はあるものの、これらのエラーは実質的にはエラーではありません。ただ、配列の2番目以降の添字を書き忘れると大抵こういったエラーが誘発されるので、エラーを素早く特定するための情報にはなります。

 このように、同じ原因からは大体同じエラーが出るので、エラーの表面的な意味よりも実践的な意味を覚えておくとよいでしょう。そして、エラーが誘発される過程を理解しようとすると、エラーに対する理解も深まり、実践的な意味を理解する手助けになるでしょう。


 他にも、多くのエラーを誘発するエラーがあります。それは、「{ } の整合性がとれていない」「構造体の名前を間違えた」「ヘッダファイルに実体を定義してしまった」というものです。

 先ず、{ } の整合性がとれていないと、それはそれは悲惨になります。先ず、関数の定義範囲がおかしくなります。すると、「関数の中で関数を定義している」「関数外に代入だの関数呼び出しだのがある」、果ては「関数の途中でファイルが終わっている」だの言われてしまいます。最終的にはエラーが一度に表示できる最大数に達することも珍しくない、最悪のエラーです。

 しかし、最悪ではありますが直すのは簡単です。こういったときは一番初めあたりにエラーがあるのでしたね。そのあたりで { } のバランスがどこかおかしくなっているはずです。そして、このせいでこのエラー以降のエラーメッセージには全く価値はなくなってしまっているので、さっさとコンパイルし直してしまいましょう。

 普通はエディタがカッコと改行のバランスを取ってくれることが多いので、このエラーはそう頻繁に起こるものではありません。{ } を何度も増やしたり減らしたりしていると、ついついバランスが崩れてしまった、ということならあるでしょうね。{ } をマクロ BEGIN END に置き換えてやっていたので、エディタの機能が活かされずバランスが崩れてしまった、というのは論外なので、そういう人は早くC/C++の文法に慣れて下さい。


 次に、構造体の名前を間違えたときです。例えば、構造体 SStudent があったとして、

SStudnet student;

と、e と n を逆さにしてしまったとします。急いで打つと、たまにあることです。もしくは、SStudnet をヘッダファイルで定義していたときに、そのヘッダファイルをインクルードし忘れていると同じ様な状態になりますね。

 このとき、コンパイラは「ある手段」を使います。それは、「SStudnet を int 型の変数の宣言と見なす」という手段です。

 そして、宣言文の後はコンマかセミコロンかを要求するわけですが、残念ながら次は student です。なので、「セミコロンがない」というエラーが出ます。そして、「ここにセミコロンがある」という「ある手段」を使います。で、次は student; ですが、ここも同じ事です。「student が宣言されていない」というエラーが出て、「student を int 型の変数の宣言と見なす」という「ある手段」を使います。

 さて、こうなると student は int 型の変数になってしまいました。そうなると後は想像がつくでしょう。student を使っているあらゆるところでエラーが出てしまうのです! そのブロックの student の関わるエラーは無視してしまいましょう。


 最後はヘッダファイルに外部リンケージを持つ実体を定義してしまったときのエラーです。これは簡単ですね。リンクエラーとしての二重定義が何個も出てくるわけです。二重定義防止コードも、リンクエラーの二重定義は防ぐことはできません。まぁ、これは第70章で話したことなので、もういいですね。


 このように、沢山エラーが出ても原因は1つであるときもあるのです。数に惑わされないようにしましょう。もちろん、原因が沢山あるときもあるので、そのときは頑張って修正しましょう。


 さて、今まではプログラムを作るときには「ビルド」(または「構築」「メイク」)を使ってきました。しかし、大きなプログラムになってくると、この時間もかなりのものになってきます。

 例えば、ヘッダファイルにエラーがあるとします。簡単な綴りの間違いでもいいです。そのエラーが出てもボーっとビルドが終わるまで待っていては時間の無駄です。ヘッダファイルのエラーは、そのヘッダファイルをインクルードした全てのファイルでエラーを出します。そして、ヘッダファイルのエラーを修正するとそのヘッダファイルをインクルードしている全てのファイルをコンパイルし直します。そのヘッダファイルがかなりの数インクルードされていると、その時間はかなりのものになります。ヘッダファイルのエラーが出たら、早めにコンパイルをうち切ってしまいましょう

 しかし、そうなる前に手を打つことができます。第70章で言った通り、コンパイルは各ファイル毎に独立に行われます。つまり、各ファイル毎、単独にコンパイルしていくことができるはずです。そして、それはもちろんできます。

 コンパイルしたいソースファイルを開き、その上でメニューの「ビルド」から「コンパイル」を選びます。Ctrl+F7 キーでもいいです。そうすれば、単独にコンパイルすることができます。

 あるヘッダファイルと、そのヘッダファイルと関係の深いソースファイルを作ったときは、先ずそのソースファイルを単独でコンパイルします。そして、エラーがなくなったところで、全体をコンパイルします。こうすれば、ヘッダファイルにエラーがあっても被害は1ファイルだけですみます。いちいちコンパイルを中断する必要もありません。

 このように、単独コンパイルを活用すれば、時間の節約になることがあります。場合によっては全体をビルドした方がいいときもあります。とりあえず全体をビルドしてエラーを大体チェックし、その後に単独コンパイルを行っていくというのも手かもしれません。それは、時と場合によってその場で自分で判断しましょう。


 では、今回の要点です。


 とうとう次回で第1部は終わりです。次回は、第2,3部のイントロダクション的な回にしたいと思っています。第2,3部は平行して書いていくつもりです。

 それでは、次回まで。


第78章 逃げの一手 | 第80章 次のステップへ

Last update was done on 2000.8.29

この講座の著作権はロベールが保有しています