2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

#ifndefに気をつけろ

Last updated at Posted at 2021-12-17

#条件ビルド
C/C++などでコードを書いている時、ある条件の時だけ有効あるいは無効にしたいコードというものがある。これはデバッグ用マクロのような話ではない。デバッグコードはリリース時に消すが、リリース用に条件Aと条件Bのバイナリを別々に作る場合のことだ。

#define INUSE_CASE_A    //CASE Aの時だけ有効にするコード
#define INUSE_CASE_B    //CASE Bの時だけ有効にするコード

#ifdef INUSE_CASE_A
CASE A用のコード
#endif

#ifdef INUSE_CASE_B
CASE B用のコード
#endif

こんなやつ。
#でも #ifndefには気を付けよう。
開発が進み、条件Cが追加された。
条件CはAまたはBの時だけ有効で、Cが定義済みなら無効にしたい。

#ifndef INUSE_CASE_C

この条件はAまたはBの時だけ有効になるコードだろうか?
正解はCが未定義の全部で有効なコードだ。
AとBが未定義でもC以外だから有効だ。それは意図したものだろうか?
開発が進み、条件Dができたとする。
AとBで有効にしたいコードはDでも有効にするべきだろうか?
#trueとfalseとnotの話
複数条件で#ifdefならtrue/falseだが、#ifndefは「以外のすべて」になる。
ついつい**#ifndef一発でいけるやん!**と思いがちだが、注意が必要だ。

#if defined(INUSE_CASE_A) || defined(INUSE_CASE_B)
AまたはBの時だけ有効にしたい処理
#endif

これならAまたはBが定義済みの場合のみ有効になる。
AとBが両方必須なら && にすればいい。これなら間違いない。
少なくとも、意図せぬ見えない条件は追加されない。

例はマクロ定義だが、コードの条件判定でも基本は同じである。
また、たとえ正常に動作すると知っていても not 判定は避けたい。

//Ex1
while(true) {
    char* buf = get_buffer_ptr();  //内部終了条件でnullptrを返す
    if(!buf) {
        終了処理
        break;
    }
    buf を使った処理;
}
//Ex2
bool bExec = true
while(bExec) {
    char* buf = get_buffer_ptr();  //内部終了条件でnullptrを返す
    if(buf) {
        buf を使った処理;
    } else {
        終了処理
        bExec = false;
    }
}

一見するとEx1のほうがシンプルだが、否定条件やbreak脱出を意識する。
否定条件は「それ以外の全部」が何を指すか考えなければならない。
否定条件を使いたくなったら「それ以外の全部でいいか?」を意識したい。

2
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?