# デバッグ: コンパイル警告メッセージ

やれやれ、条件分岐は難しかった。この辺でもう一度ひと休みして、息抜きとしてデバッグの話をしよう。今回はコンパイラーの警告メッセージ(warning messages)についてだ。

コンパイラーはソースコードに文法エラーや意味エラーがあると、エラーメッセージを出すことはすでに学んだ。

コンパイラーがエラーメッセージを出さなかったとき、コンパイラーはソースコードには文法エラーや意味エラーを発見できず、コンパイラーは意味のあるプログラムを生成することができたということを意味する。しかし、コンパイルが通って実行可能なプログラムが生成できたからといって、プログラムにバグがないことは保証できない。

たとえば、変数xyを足して出力するプログラムを考える。

int main()
{
    auto x = 1 ;
    auto y = 2 ;

    std::cout << x + x ;
}

このプログラムにはバグがある。プログラムの仕様は変数xyを足すはずだったが変数xxを足してしまっている。

コンパイラーはこのソースコードをコンパイルエラーにはしない。なぜならば上のコードは文法的に正しく、意味的にも正しいコードだからだ。

警告メッセージはこのような疑わしいコードについて、エラーとまではいかないまでも、文字どおり警告を出す機能だ。例えば上のコードを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のはずだ。なぜか0123は等しいと判断されてしまった。いったいどういうことだろう。

この謎は警告メッセージを読むと解ける。

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型に変換されるということだ。0false、非ゼロは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だ。

警告メッセージは万能ではない。ときにはまったく問題ないコードに対して警告メッセージが出たりする。これは仕方がないことだ。というのもコンパイラーはソースコード中に表現されていない、人間の脳内にある意図を読むことはできないからだ。ただし、警告メッセージにはひと通り目を通して、それが問題ない誤検知であるかどうかを確認することは重要だ。