はじめに
最近C言語を書き始めたという真のC言語初心者向けの記事です。
意図通りに動かないプログラム
コンパイルは通るものの意図通りに動かないプログラムを書きます。
0~15の数字のうち奇数を降順に表示することを意図したプログラムです。
# include <stdio.h>
int main(void)
{
unsigned int i;
int mod;
int num;
for(i=15; 0<=i; i--){
/* 2で割った余りを求める */
mod = i % 2;
if( mod = 1){
/* 余りが1の時は奇数なので表示 */
printf("%d ", i);
}else{
/* 偶数の時は何もしない */
}
}
return 0;
}
実行してみましょう
./a.out
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78...
プログラムが暴走しています。一体どこが悪いのか・・・?
「こんなのすぐ分かるじゃんw」と思ったそこのあなたは思い出してください。
参考書を見ながらfor文を書き、コンパイルを通すのでいっぱいっぱいだったあの頃を・・・。
とにもかくにも意図通りに動かないのでデバックの時間です。
ソースコードを1行目から検証していきますか?それともステップ実行して動かしながら見ていきますか?
いえいえちょっと待ってください。それよりもっと効率的、かつ正確に原因の個所を特定できる方法があるのです。
それがコンパイラの警告(warning)です!
警告を出してみましょう
gcc main.c
多分こんな感じでコンパイルしていると思います。ここにオプション「-Wall -Wextra」をつけてコンパイルしてみましょう。
gcc main.c -Wall -Wextra
するとコンパイル時、下記の出力が出てくるではありませんか。
main.c: In function ‘main’:
main.c:9:16: warning: comparison of unsigned expression >= 0 is always true [-Wtype-limits]
for(i=15; 0<=i; i--){
^
main.c:12:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
if( mod = 1){
^
main.c:7:9: warning: unused variable ‘num’ [-Wunused-variable]
int num;
^
これがコンパイラの警告です。
「ただでさえプログラム書くのでいっぱいいっぱいなのに英語か・・・もうだめだぁ・・・おしまいだぁ」
と思ったあなた。安心してください。コピーしてGoogle翻訳に突っ込んでみましょう。
原文
main.c:9:16: warning: comparison of unsigned expression >= 0 is always true [-Wtype-limits]
翻訳後
main.c:9:16:警告:符号なし式> = 0の比較は常にtrueです[-Wtype-limits]
コンパイラの警告くらいの英文であれば「なんとなくわからんことはない」程度には翻訳してくれます。
警告とコードを照らし合わせる
main.c:9:16:警告:符号なし式> = 0の比較は常にtrueです[-Wtype-limits]
太字にしたとこに注目です。ここが警告対象のコードの位置を教えてくれています。
ソースファイルのmain.cの9行目に対して警告を発してくれているというわけです。見てみましょう。
for(i=15; 0<=i; i--){
一見何も問題ないように見えます。警告の内容と照らし合わせてみましょう。
「"符号なし式> = 0"とはこの行で不等号を使っているのは「 0<=i」だけだからここのことかな?、これが常にtrue・・・?
for文の真ん中の式は継続条件でこれがtrueだったらループは永遠に続いてしまう。でもiは「i--」で減算してるし・・・。
ん?符号なし式・・・そういえばiの型はなんとなく覚えていたばかりのunsigned int型で宣言していた・・・ㇵッ!」
符号なし型を使用していたが故にいくら引いてもiが0未満になることはなく、プログラムは暴走していたのです。
これは初心者の場合ノーヒントだと中々看破しにくいバグでしょう。コンパイラさんありがとう!
そんなわけで変数iの型をint型に修正してコンパイルしなおし実行ッ!勝ったッ!第3部完!
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
が・・・・・駄目っ・・・・・!奇数だけでなく偶数も表示されてしまっています。
しかし落ち着きましょうまだ警告は出ています。
原文
main.c:12:9: warning: suggest parentheses around assignment used as truth value [-Wparentheses]
main.c:7:9: warning: unused variable ‘num’ [-Wunused-variable]
翻訳後
main.c:12:9:警告:真偽値として使用される代入の前後にかっこを表示する[-Wraparenteses]
main.c:7:9:警告:未使用の変数 'num' [-Wunused-variable]
「真偽値として使用される~」はなんじゃそりゃって感じですが該当箇所を見てみましょう。
if( mod = 1){
おっと、「==」で比較しようとしていたところを代入にしてしまっていたわけです。
単純なケアレスミスですが疲れてるときにコード書いたりしているとやりがちです。
この程度の規模ならすぐ気が付きますが、行数が多かったりする場合だとつい見逃してしまう時もあります。
メジャーどころのコンパイラであればこの手のケアレスミスは大抵拾ってくれます。ありがたや。
「未使用の変数 'num' [-Wunused-variable]」のほうも見てみましょう。
int num;
確かにnumは宣言したもののどこからも使われていません。
処理に影響ないし残したままでいいか・・・とは思わず消しておきましょう。
処理に影響のない警告が大量に出力されてるが故に重要な警告を見逃してしまう場合もありますから。
# include <stdio.h>
int main(void)
{
int i;
int mod;
for(i=15; 0<=i; i--){
/* 2で割った余りを求める */
mod = i % 2;
if( mod == 1){
/* 余りが1の時は奇数なので表示 */
printf("%d ", i);
}else{
/* 偶数の時は何もしない */
}
}
return 0;
}
警告箇所を修正した上記のコードをコンパイルして実行しましょう。もう警告は出ていませんね。
./a.out
15 13 11 9 7 5 3 1
やったぜ。
まとめ
コンパイラの警告は意図通りに動かないコードの怪しい箇所を特定する上で非常に有用です。
特にケアレスミスはソースコードを追ってるだけでは見逃してしまう場合もあるので、警告を見る癖があるかないかで対処の時間が大幅に変わってきます。
出来立てホヤホヤのプログラムが異常な動きをしている場合は机上検証等を行う前にまずコンパイラさんの警告に耳を傾けてみましょう。
警告は最大レベルで出して、必ずチェックする癖は早いうちからつけておきましょう!