やれやれ、条件分岐は難しかった。この辺でもう一度ひと休みして、息抜きとしてデバッグの話をしよう。今回はコンパイラーの警告メッセージ(warning messages)についてだ。
コンパイラーはソースコードに文法エラーや意味エラーがあると、エラーメッセージを出すことはすでに学んだ。
コンパイラーがエラーメッセージを出さなかったとき、コンパイラーはソースコードには文法エラーや意味エラーを発見できず、コンパイラーは意味のあるプログラムを生成することができたということを意味する。しかし、コンパイルが通って実行可能なプログラムが生成できたからといって、プログラムにバグがないことは保証できない。
たとえば、変数x
とy
を足して出力するプログラムを考える。
int main()
{
auto x = 1 ;
auto y = 2 ;
std::cout << x + x ;
}
このプログラムにはバグがある。プログラムの仕様は変数x
とy
を足すはずだったが変数x
とx
を足してしまっている。
コンパイラーはこのソースコードをコンパイルエラーにはしない。なぜならば上のコードは文法的に正しく、意味的にも正しいコードだからだ。
警告メッセージはこのような疑わしいコードについて、エラーとまではいかないまでも、文字どおり警告を出す機能だ。例えば上のコードをGCCでコンパイルすると以下のような警告メッセージを出す。
$ make
g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program
main.cpp: In function ‘int main()’:
main.cpp:5:10: warning: unused variable ‘y’ [-Wunused-variable]
auto y = 2 ;
^
すでに説明したように、GCCのメッセージは
ソースファイル名:行番号:列番号:メッセージの種類:メッセージの内容
というフォーマットを取る。
このメッセージのフォーマットに照らし合わせると、このメッセージはソースファイルmain.cpp
の5行目の10列目について何かを警告している。警告はメッセージの種類としてwarning
が使われる。
警告メッセージの内容は、「未使用の変数'y'
[-Wunused-variable]
」だ。コード中で'y'
という名前の変数を宣言しているにもかかわらず、使っている場所がない。使わない変数を宣言するのはバグの可能性が高いので警告しているのだ。
[-Wunused-variable]
というのはGCCに与えるこの警告を有効にするためのオプション名だ。GCCに-Wunused-variable
というオプションを与えると、未使用の変数を警告するようになる。
$ g++ -Wunused-variable その他のオプション
今回は-Wall
というすべての警告を有効にするオプションを使っているので、このオプションを使う必要はない。
もう1つ例を出そう。以下のソースコードは変数x
の値が123
と等しいかどうかを調べるものだ。
int main()
{
// xの値は0
auto x = 0 ;
// xが123と等しいかどうか比較する
if ( x = 123 )
std::cout << "x is 123.\n"s ;
else
std::cout << "x is NOT 123.\n"s ;
}
これを実行すると、"x is 123.\n"
と出力される。しかし、変数x
の値は0
のはずだ。なぜか0
と123
は等しいと判断されてしまった。いったいどういうことだろう。
この謎は警告メッセージを読むと解ける。
g++ -std=c++17 -Wall --pedantic-error -include all.h main.cpp -o program
main.cpp: In function ‘int main()’:
main.cpp:5:12: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
if ( x = 123 )
~~^~~~~
main.cpp
の5行目の12列目、「真偽値として使われている代入は括弧で囲むべき」とある。これはいったいどういうことか。よく見てみると、演算子が同値比較に使う==
ではなく、=
だ。=
は代入演算子だ。
int main()
{
auto x = 0 ;
// 代入
// xの値は1
x = 1 ;
// 同値比較
x == 1 ;
}
実はif文
の条件
にはあらゆる式
を書くことができる。代入というのは、実は代入式
という式なので、if文
の中にも書くことができる。その場合、式の結果の値は代入される変数の値になる。
そして思い出してほしいのは、整数型はbool
型に変換されるということだ。0
はfalse
、非ゼロはtrue
だ。
int main()
{
auto x = 0 ;
// 1はtrue
bool b1 = x = 1 ;
if ( x = 1 ) ;
// 0はfalse
bool b0 = x = 0 ;
if ( x = 0 ) ;
}
つまり、"if(x=1)"
というのは、"if(1)"
と書くのと同じで、これは最終的に、"if(true)"
と同じ意味になる。
警告メッセージの「括弧で囲むべき」というのは、括弧で囲んだ場合、この警告メッセージは出なくなるからだ。
int main()
{
auto x = 0 ;
if ( (x = 0) )
std::cout << "x is 123.\n"s ;
else
std::cout << "x is NOT 123.\n"s ;
}
このコードをコンパイルしても警告メッセージは出ない。
わざわざ括弧で囲むということは、ちゃんと代入を意図して使っていることがわかっていると意思表示したことになり、結果として警告メッセージはなくなる。
この警告メッセージ単体を有効にするオプションは-Wparentheses
だ。
警告メッセージは万能ではない。ときにはまったく問題ないコードに対して警告メッセージが出たりする。これは仕方がないことだ。というのもコンパイラーはソースコード中に表現されていない、人間の脳内にある意図を読むことはできないからだ。ただし、警告メッセージにはひと通り目を通して、それが問題ない誤検知であるかどうかを確認することは重要だ。