最近やってしまったバグ①
最近やってしまったバグたち。
またやるかもしれないので、備忘のために書いてみます。
1.for 文のループカウンタのインクリメントが無い。
ループカウンタ "i" が大きくならないので、for ループを永遠に抜けません。
for (int i = 0; i < I_MAX; ) {
// 本来はこう
// for (int i = 0; i < I_MAX; i ++) {
hogehoge();
}
このバグはビルドしてもエラーにはなりません。
また、他のバグと併発していたので、なかなか気づきませんでした。
工程が遅れていて、焦った状態で実装した後のデバッグで発生しました。
2.ポインタの構造体のメンバが null になっている
割り当てられていないメモリ領域を参照しにいって、プログラムが途中で落ちます。
例えば、構造体の中に別の構造体のポインタがあって、そのポインタが対象の構造体のアドレスを割り当てられていない状態で使われてしまった時になります。
typedef struct{
int a;
}HOGE1_t;
typedef struct{
HOGE1_t *pHoge1;
}HOGE2_t;
void main(){
HOGE1_t hoge1;
HOGE2_t hoge2;
// このpHoge1 に構造体hoge1のアドレスを設定するのを忘れていた。
// hoge2.pHoge1 = &hoge1;
hogeTest(&hoge2);
}
void hogeTest(HOGE2_t *pHoge2){
// ここで設定されていない構造体 pHoge1 の変数 a を参照して落ちる。
pHoge2->pHoge1->a = 1;
}
上記のサンプルコードでは、hogeTest() に構造体ポインタ hoge2 を渡す前にメンバ変数 pHoge1 を設定するのを忘れていました。そして、hogeTest()の中で pHoge1 を使ったためプログラムが落ちてました。
このバグもビルドしてもエラーにはなりません。
これも工程が遅れていて、焦った状態で実装した後のデバッグで発生しました。
3.プリプロセッサに登録済と思った条件付きコンパイル シンボルがない
条件付きコンパイルが効きません。
#ifndef "コンパイルシンボル" ~ #endifで囲んだ部分が無効になりません。
例えば、UnitTest 実行時に HAL ドライバのマクロなどを実行するとプログラムが落ちてしまうことがあります。そこで、UnitTest プロジェクトのプリプロセッサの設定に"FOR_UNIT_TEST"という定義を追加しておけば、#ifndef FOR_UNIT_TEST ~ #endif で囲むと、囲まれた部分は実行されずにスルーすることができます。
void intHoge(HOGE1_t pHoge1){
#ifndef FOR_UNIT_TEST
// FOR_UNIT_TEST が定義されていれば、ここは実行されない
HAL_GPIO_HOGE();
#endif
pHoge1->a ++;
}
上記のサンプルコードでは、UnitTest プロジェクトから関数 intHoge() が実行されたとき、HAL_GPIO_HOGE() を実行しません。
そう。"FOR_UNIT_TEST" さえ定義されていれば!
そうでなければ、HAL_GPIO_HOGE() は実行されてしまいます。
自分一人で開発するときは、本人が設定するので、問題無いです。
しかし、今回は多人数開発だったので「誰か設定しているだろう」と思って、自分で確認せずに済ませてしまいました (T_T)
さらに、設定の確認もややこしいです。
それは、1つのプロジェクトでC や C++ などの複数のコンパイラを扱っていて、C では追加されているけど C++ では追加されていないこともあるからです。
今回は C++ でのみ有効になっていたので、ヘッダーファイルでは条件付きコンパイルができてCファイルではできないという現象になっていました。
なので、余計に気づくのに時間がかかりました。
もちろん、ビルドしてもエラーにはなりません。
現象が発生したときに可能性の一つとして頭に入れておくべきでした。
すっかり、環境設定済みと思い込んでいましたね。
まとめ
以上のバグは実行すれば、気づくものでした。
特に最初の2つバグは実装段階で気づくべきものだったと思います。
基本的な対策ですが、
落ち着いて実装する
書いたら見直す
と言うのが、大事なんだろうなと思います。