警告とデバッグ情報(-Wall -g)
コンパイルが通っても、潜在的なバグが残っていることはよくあります。-Wall を付けると未使用変数や型の取り違えなど、多くの警告をコンパイラが教えてくれます。さらに厳しく見たいときは -Wextra も加えます。一方 -g はデバッグ情報を実行ファイルに埋め込むオプションで、これを付けてビルドしておくと後で gdb を使ったとき、行番号や変数名つきで追跡できます。開発中は gcc -Wall -g prog.c -o prog のように両方を付けるのが定番です。
コンパイルがエラーなく通ると、つい「これで正しいプログラムができた」と安心しがちだ。だが、ここに落とし穴がある。
コンパイルが通ることと、プログラムが正しく動くことは別の話なのだ。文法的には問題なくても、論理的なミスや危うい書き方が残っていることはよくある。
たとえば値を入れたつもりの変数を実は使っていない、型を取り違えている、初期化し忘れた変数を読んでいる。こうした潜在的なバグは、放っておくと実行時に予期せぬ動作を引き起こす。
ありがたいことに、gcc はこうした「文法エラーではないが怪しい箇所」を警告(warning)として教えてくれる能力を持っている。ただし、その多くは既定では表示されない。
その能力を引き出し、警告を積極的に出させてデバッグの準備を整えるためのオプションが、開発では欠かせない。
🔦 怪しさを洗い出す最初の一手 -Wall
潜在的な問題を洗い出す最初の一手が -Wall だ。これを付けると、未使用の変数、初期化前の変数の使用、型の取り違え、戻り値の暗黙の扱いなど、よくある危険のパターンに対して幅広く警告が出るようになる。
名前の Wall は warning all のように見えるが、実際には「すべての警告」ではなく「有効にすべき重要な警告のひとまとまり」を指す。
それでも既定よりはるかに多くの問題に気づけるため、開発中はまず -Wall を付けるのが常識とされている。
たとえば gcc -Wall prog.c -o prog と打つと、コンパイルは通っても warning: 〜 という指摘が並ぶことがあり、それが将来のバグの芽を事前に摘む手がかりになる。
具体例を挙げよう。if (x = 5) のように、比較のつもりが代入になっている書き間違いは、文法上は正しいためエラーにはならないが、-Wall を付けると「代入が条件として使われている」と警告してくれる。
こうした「動いてしまうが意図と違う」コードを早期に見つけられるのが大きな価値だ。
🔎 さらに細かく見る -Wextra
-Wall でも拾いきれない、より細かい怪しさまで見たいときは -Wextra を加える。
-Wextra は -Wall に含まれない追加の警告を有効にするオプションで、たとえば比較の符号の食い違いや、使われない関数引数など、より踏み込んだ指摘をしてくれる。
両方を組み合わせて gcc -Wall -Wextra prog.c -o prog とすると、かなり厳しいチェックがかかる。
プログラムの品質を高めたいときや、人に渡すコードを書くときは、この2つをセットで付けるとよい。
さらに厳格にしたい現場では、警告をエラー扱いにして必ず直させる -Werror を併用することもあるが、まずは -Wall を基本に、必要に応じて -Wextra を足す、という段階で十分だ。
🐞 デバッグ情報を埋め込む -g
警告とは別の目的を持つのが -g だ。これはデバッグ情報を実行ファイルに埋め込むオプションである。
なぜそんなものが要るのか。通常のコンパイルでは、できあがった機械語と、元のソースコードの行番号や変数名との対応関係は失われてしまうからだ。
実行ファイルにとって変数の名前は本来不要なので、最終的な機械語には残らない。そのままデバッガで追っても、機械語のアドレスしか分からず、人間には読み解きづらい状態になる。
-g を付けてビルドしておくと、この対応関係(どの機械語がソースの何行目か、変数の名前は何か、構造体の形はどうか)がデバッグ情報として実行ファイル内に保存される。
その結果、後で gdb のようなデバッガを使ったとき、プログラムの実行を一時停止させる地点であるブレークポイントを行番号や関数名で設定でき、「ソースの何行目で止まり、変数 x の値はいくつか」といった形で、ソースコードに即した分かりやすい追跡ができるようになる。
-g を付けても実行速度そのものは変わらず、ファイルサイズがデバッグ情報のぶん大きくなるだけなので、開発中は気軽に付けてかまわない。
🎯 開発中の定番の組み合わせ
以上を踏まえると、開発中のコンパイルでは -Wall と -g を両方付けるのが定番になる。具体的には gcc -Wall -g prog.c -o prog のように書く。
-Wall で書いている最中の怪しい箇所をその場で潰し、-g でいつデバッガを使うことになっても困らないようにしておく、という二段構えだ。両者は目的が違うので競合せず、同時に付けて問題ない。
慣れてきたら -Wextra を加えた gcc -Wall -Wextra -g prog.c -o prog をひな型にしてもよい。
ひとつ注意がある。最適化オプション(-O2 など)と組み合わせると、最適化の影響で変数が消えたり行の対応がずれて見えたりすることがあるため、純粋にデバッグしたい段階では最適化を外しておくほうが追いやすくなる。
⚠ つまずいたら、まずここ
ありがちな失敗は、警告を「エラーではないから」と無視してビルドを続けてしまうことだ。
warning が出てもコンパイル自体は通り実行ファイルもできてしまうため、つい後回しにしがちだが、warning は将来のバグ予告であることが多く、放置すると後で原因不明の不具合として跳ね返ってくる。
とくに大量の警告が出る状態に慣れてしまうと、本当に危険な1件が他に埋もれて見えなくなる、という二次被害も起きる。
だからこそ、出た警告は基本的にその場で1つずつ解消し、警告ゼロの状態を保つ、という姿勢が結局は近道だ。
もう1つの失敗は、-g を付けずにビルドした実行ファイルをいざデバッグしようとして、行番号も変数名も見えずに困るパターンである。後からデバッグする可能性が少しでもあるなら、最初から -g を付けておくのが賢明だ。
完成品をユーザーに配るときは、サイズを小さくしデバッグ情報を含めない別ビルドにする、という使い分けもある。
だが開発のあいだは「-Wall で品質を保ち、-g で調査に備える」を基本動作にしておけば、コンパイラを単なる翻訳機ではなく頼れる相棒として使いこなせる。