変数の初期化をサボるな,それから -Wshadow オプションを使え

という自戒.前にも似たような話を書いたのだが,懲りずにまたやった.
今回は,未初期化の変数と未発見のバグを含んだプログラムを使っていて,まったく関係のないコードを加えたタイミングで変数の初期値変動によってバグが顕在化した場合に,新しく加えたコードがバグの原因のように見えてしまって,デバッグに苦労する,という話.で,この手のトラブルを避けるには,変数を必ず初期化し,gccなら -Wshadow オプションを使おう,という内容.

サンプルコード:

#include <iostream>
using namespace std;
double func1(int a)
{
  double x;
  if(a>0)
  {
    x=10.0;
    ++x;
  }
  else
  {
    double x= -10.0;
    --x;
  }
  return x;
}
// double func2()
// {
//   double x=2;
//   double y=3;
//   return x+y;
// }
int main()
{
  // cout<<func2()<<endl;
  cout<<func1(5)<<endl;
  cout<<func1(-1)<<endl;
  return 0;
}

func2 はコメントアウトしたままで.
これをコンパイルし実行すると,

11
-1.01923e-41

みたいな結果になる.何のことかというと,func1 の else の中で, double x を間違ってもう一回定義してしまっていて,main の2回目の func1 呼び出しでは初期化されていない値が返っている,という例.
このバグに気づかなかったとしよう.もっと長いプログラムで,結果も複雑だったとして.
次に,func2 のコメントアウトを消して(mainのほうも),コンパイルし,実行すると,

5
11
11

のような結果に.つまり,mainでの2回目の func1 呼び出しで,初期化されていない値が返るのだが,その内容が func2 の実行によって変わってしまった,という例.
で,この時点で,func1 のバグが顕在化したとする.しかも,一見 func1 とは関係していなさそうな形で.
まずは,func2 を疑う.func2 を追加するまでは,動いていた,という認識だから.
それから,func1 にたどり着くまでに,かなりの時間がかかる.
# というか,かかった.

このようなバグを減らすために:

  1. 変数の初期化は,必ずやる.上の例でも,どうせ if か else で代入される,,,と考えてしなかったが,初期化しておけば,少なくとも func2 によって値が変わることはない.
  2. gcc の場合,-Wshadow オプションをつけてコンパイルする(-Wall には含まれない).上記の例だと,「警告: declaration of ‘x’ shadows a previous local [-Wshadow]」という警告を出してくれる.