C言語 ifdef 【プログラムをカットする技術と使い方を紹介】

C言語
この記事は約18分で読めます。

こんにちは、ナナです。

プリプロセッサ3大機能のうちの1つ「ifdef」機能について解説しましょう。

プリプロセッサ3大機能
  • #define マクロ定義
  • #include インクルード
  • #ifdef 条件コンパイル

「ifdef」を利用することで不要なプログラムを無効化したり、異なるプログラムを切り替えたりすることができるようになります。

使いこなすことで、かなり強力な武器になる機能ですので習得しておきましょう。

本記事では次の疑問点を解消する内容となっています。

本記事で学習できること
  • ifdefを使った条件コンパイルの機能とは?
  • ifdefの基本的な使い方はどうするの?
  • ifdefをよく使う利用シーンとは?
  • ifdefの書き方ってどんなパターンがあるの?
  • ifdefを利用したデバッグ処理の埋め込み方とは?

では、ifdefの条件コンパイル機能の使い方を学んでいきましょう。

プリプロセッサが何かわからない方は、『C言語 プリプロセッサ【絶対知るべき3大機能を順に解説する】』の記事を先に見ておくとよいでしょう。

スポンサーリンク

ifdef 条件コンパイルとはどのような機能なのか?

押忍!今日の講義は「条件コンパイル」ってやつっすね。で、条件…コンパイル…全部わかんないっす…。なんなんすか、これは?

ナナ
ナナ

「条件コンパイル」っていうのは「切り絵師」のことだよ。依頼に応じてなんでも切ってくれるんだよ。では、先生!「鶴」をお願いします。

切り絵師
切り絵師

鶴でござんすね、たやすい御用で。チョキ、チョキ、チョチョチョ、チョキ、チョキ。

ifdefは「条件コンパイル」と呼ばれる機能であり、皆さんが作ったプログラムの中で不要な部分をカットする機能なんです。

条件コンパイルのイメージ

「不要な部分なんてないよ!」って思うかもしれませんが、いろいろと用途があるんです。

スポンサーリンク

ifdef 条件コンパイルの基礎的な使い方とイメージ

押っ忍?!切り抜いた鶴が飛んでいったっす‼
知りたいっす。「条件コンパイル」の秘密を教えてくださいっす!先生!

切り絵師
切り絵師

鶴になったのは、あっしの能力なんで、「条件コンパイル」とは関係ないんですぜ。でも、ようござんしょ。

切り絵の基礎を、まずは教えて差し上げましょう。

条件コンパイルは「if文」に文法が似ています。まずは、基本的な書き方から覚えていきましょう。

条件コンパイルの書き方

さあ、条件コンパイルの一番代表的な書き方が次のものです。

条件コンパイルの書き方

#if 0
  カットしたい処理
#else
  残したい処理
#endif

#if 1
  残したい処理
#else
  カットしたい処理
#endif

#elseの記述は省略することも可能である。

では、具体的なプログラムで使い方を見ていきましょう。

条件コンパイルを使ったサンプルプログラムの紹介

次のように書くと、#if 0 ~ #endif までのプログラムがカットされ「World」のみが画面に出力されます。

皆さんも是非、動かしてみてください。

#include <stdio.h>

int main(void)
{
#if 0
    printf("Hello");  // カットされる
#endif
    printf("World");

    return 0;
}
World

続いてこの「#if 0」を「#if 1」に変更すると、カットされなくなります。

#include <stdio.h>

int main(void)
{
#if 1
    printf("Hello");  // カットされない
#endif
    printf("World");

    return 0;
}
HelloWorld

このように「条件コンパイル」を使うと、プログラムの一部を「カットする」「残す」というのを、簡単に切り替えることができるようになります。

スポンサーリンク

ifdef 条件コンパイルを使う代表的なシーン

先生!これってどんな時に使うんすか?いらない処理なら、いっそのこと削除したらいいじゃないっすか?

切り絵師
切り絵師

おまえさん、プログラムを作ってて「この部分の処理を一時的に無効化したいなぁ」って思ったことはないでやんすか?

そんな時にこの「条件コンパイル」を使うと、プログラムは残ってやんすが、動かないようにできるでやんすよ。

条件コンパイルを使う代表的なシーンは「コメントアウト」です。

プログラムを書いていると「この処理は今だけ動かないようにしたい。でも処理は残しておきたい」なんてシーンはよくあります。

下記は、「A」と「B」の文字を10回表示するプログラムと実行結果です。

#include <stdio.h>

int main(void)
{
    int i;

    //  「A」を画面に出力
    for (i = 0 ; i < 10 ; i++)
    {
        printf("A");    /* A */
    }

    //  「B」を画面に出力
    for (i = 0; i < 10; i++)
    {
        printf("B");    /* B */
    }

    return 0;
}
AAAAAAAAAABBBBBBBBBB

皆さんに、このプログラムの 「”A”の表示処理を、動かないようにしてください」とお願いしたら、どのように対応しますか?

多くの方が次のようにコメントアウトするのではないでしょうか?

#include <stdio.h>

int main(void)
{
    int i;

//  //  「A」を画面に出力
//  for (i = 0; i < 10; i++)
//  {
//      printf("A");    /* A */
//  }

    //  「B」を画面に出力
    for (i = 0; i < 10; i++)
    {
        printf("B");    /* B */
    }

    return 0;
}

皆さん、このコメントアウトって面倒くさいって思いませんか?

5行に渡ってわざわざ「//」を書いて動かないようにする。じゃあ、これが30行分あったらどうでしょう?やってられませんよね。

こんな時に便利なのが、条件コンパイルによるコメントアウトです。

#include <stdio.h>

int main(void)
{
    int i;

#if 0  // 処理を無効化
    //  「A」を画面に出力
    for (i = 0; i < 10; i++)
    {
        printf("A");    /* A */
    }
#endif

    //  「B」を画面に出力
    for (i = 0; i < 10; i++)
    {
        printf("B");    /* B */
    }

    return 0;
}

#if と #endif の2行分は書かないといけませんが、30行のコメントアウトも簡単に可能です。

しかも、この条件コンパイルのメリットは、再度処理を有効化したい時は「#if 0」を「#if 1」に変えるだけで対応することができます。

切り絵師
切り絵師

切り絵師ってぇのは、いらねぇところを切るだけでなく、必要な部分を残すこともできて切り絵師なんですぜぇ。それがプロの技ってもんよっ!

スポンサーリンク

ifdef 条件コンパイルの記述パターン

押忍っ、先生っ!条件コンパイルの「#if 0」をマスターしたっすよ。自分も今日から切り絵師デビューっすね。

切り絵師
切り絵師

おまえさん、切り絵の世界をなめちゃいけねーよ。「#if 0」だけが条件コンパイルじゃねぇぜ。いろんなテクニックを磨いてから出直してきなっ!

条件コンパイルは結構いろいろな使用パターンがあります。代表的な書き方を紹介しましょう。

#if 0 ~ #else ~ #endifによる条件コンパイル

この例はすでに紹介している一番代表的な条件コンパイルの書き方です。

明るくなっている部分がカットされるコード箇所になります。

#if 0による条件コンパイル

#include <stdio.h>

int main(void)
{

#if 0
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

#if 1による条件コンパイル

#include <stdio.h>

int main(void)
{

#if 1
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

このように「#else」を使って有効/無効部分を切り替えることも可能です。

#if マクロ定義名 ~ #else ~ #endifによる条件コンパイル

#if に続く数値はマクロ定義名を書くこともできます。マクロ定義と条件コンパイルの合わせ技のテクニックです。

DEBUG_FLGマクロが「0」で定義

#include <stdio.h>

#define DEBUG_FLG   (0)

int main(void)
{

#if DEBUG_FLG
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

DEBUG_FLGマクロが「1」で定義

#include <stdio.h>

#define DEBUG_FLG   (1)

int main(void)
{

#if DEBUG_FLG
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

このようにマクロ定義を組み合わせて条件コンパイルを連動するケースもよく利用されます。

#ifdef マクロ定義名 ~ #else ~ #endifによる条件コンパイル

さぁ、ようやく登場の「ifdef」による条件コンパイルですね。この場合もマクロ定義を組み合わせます。

ただし、この場合はマクロ定義に数値を指定せず、マクロ名をdefineするかどうかで切り替えます。例を示しましょう!

DEBUG_ONマクロが定義無効

#include <stdio.h>

//#define DEBUG_ON

int main(void)
{

#ifdef DEBUG_ON
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

DEBUG_ONマクロが定義有効

#include <stdio.h>

#define DEBUG_ON

int main(void)
{

#ifdef DEBUG_ON
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

ifdefとは、if(もし)def(define定義)であり、「もし、define定義がされていれば」という意味です。そのため、このように利用します。

#ifndef マクロ定義名 ~ #else ~ #endifによる条件コンパイル

「ifdef」に非常に似ている「ifndef」という条件コンパイルもあります。

「ifndef」とは、if(もし)、not(されていない)、def(define定義)というものであり、「もし、define定義がされていなければ」という意味になります。

つまり、「ifdef」とは逆の関係性になります。

DEBUG_ONマクロが定義無効

#include <stdio.h>

//#define DEBUG_ON

int main(void)
{

#ifndef DEBUG_ON
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

DEBUG_ONマクロが定義有効

#include <stdio.h>

#define DEBUG_ON

int main(void)
{

#ifndef DEBUG_ON
    printf("Hello");
#else
    printf("World");
#endif

    return 0;
}

カットされる場所が「ifdef」とは逆になっているのがわかりますね。

この「ifndef」条件コンパイルは、ヘッダファイルと呼ばれるファイルを構築するときに必ず必要になるものです。

ヘッダファイルに関しては、カリキュラム終盤の『C言語 ヘッダファイルの書き方【サンプルフォーマットを公開】』にて解説します。すでにヘッダファイルを知る力がある方は読んでおくとよいでしょう。

切り絵師
切り絵師

これらが基本となる条件コンパイルの書き方でやんすよっ!皆さん、しっかり覚えなさんな。

スポンサーリンク

ifdef 条件コンパイルを使ったデバッグ処理の埋め込み

押忍っ!先生!条件コンパイルは不要なプログラムをカットする使い方が基本っすね。基本ってことは他にも使うってことっすか?

切り絵師
切り絵師

そうでやんすな~。条件コンパイルは機能の切り替えにも使うでやんすよ。アイデア次第で、いろんなことができるでやんす。奥が深い世界でやんすよ。

条件コンパイルは、デバッグ用の処理を埋め込むときにも利用されます。

実際の開発ではデバッグ工程を経てリリース工程に進みます。ただし、リリース工程に進んだとしても、何か問題が起きた時のために「デバッグ用の処理を残しておきたい!」ということはよくあるのです。

ここで紹介する代表的なデバッグ用処理は「ロギング機能」です。

プログラムが動作している痕跡をprint文によるログとして出力することで、問題を可視化できるようにします。

しかし、リリース工程ではprint文のログは出したくないということがあります。このような場面で条件コンパイルは利用されます。

#include <stdio.h>

// デバッグ時に有効化
#define D_DEBUG_ON

int add(int num1, int num2)
{
#ifdef D_DEBUG_ON
    printf("num1:%d num:%d \n", num1, num2);
#endif
    return num1 + num2;
}

int main(void) {
    int num;
    num = add(10, 20);

#ifdef D_DEBUG_ON
    printf("num:%d\n", num);
#endif

    return 0;
}

D_DEBUG_ONのマクロ定義をするか否かで、printf関数の処理を出すか出さないかを一括で切り替えることができます。

このようにマクロ定義と条件コンパイルを組み合わせることで、より強力なコードのカット処理が実現できるようになります。

スポンサーリンク

Q&A:ifdef 条件コンパイルに関するよくある質問

切り絵師
切り絵師

お前さんたち、なんか聞きたいことあるんならいいなさんな。今なら、なんでも答えてあげますぜぇ。

Q:条件コンパイルによるコメントアウトは/**/でもいいんじゃない?

先生!物言いがあるっす。先生は知らないかもしれないっすけど、C言語でコメントアウトする方法には「/* コメント */」なんて書き方もあるっすよ。

これを使えば、複数行でもコメントアウトできるんっす!

切り絵師
切り絵師

おまえさん、あっしを侮っちゃいけないでやんすよ。あっしは元ITエンジニアでやんす。C言語はお手の物なんでやんす。

その方法はお勧めしないんでやんすよ。

C言語でコメントアウトする方法には「//コメント」以外に、「/* コメント */」も確かに使えましたね。

このコメントならば、次のように複数行のコメントアウトが可能です。

#include <stdio.h>

int main(void)
{

/*
    printf("Hello");
    printf("World");
*/
    return 0;
}

こんな感じですね。しかし、このコメントアウトには欠点があるのです。それはコメントのネストができないのです。

#include <stdio.h>

int main(void)
{

/*
    printf("Hello");    /* コメント */
    printf("World");
*/
    return 0;
}

このように「/* */」の範囲の中にもう一つの「/* */」があった場合、コメントアウトが正常にできません。つまり、ネスト構造の場合に対応できないのです。

これに対し、条件コンパイルはネストに対応しています。

次のように「#if」の中に「#if」が存在しても正しくカットしてくれます。

#include <stdio.h>

int main(void)
{

#if 0
    printf("Hello");
#else

#if 1
    printf("World");
#else
    printf("Season2");
#endif

#endif
    return 0;
}

このプログラムを動かすと「World」のみが表示されることになります。

Q:条件コンパイルの#ifって普通のif文でも書けそうだけど違うの?

先生!条件コンパイルってif文に似てるっす。ていうか、ほぼ一緒っす。if文でいんじゃないっすか?でも、先生のことは尊敬してるっす。

切り絵師
切り絵師

おまえさん、切り絵師のことが全然わかってないでやんすね。

あっしらの仕事は「カットする」ってことなんすよっ。「カットする」ってこたぁ、「なくしちまう」ってことなんでぃ!この違いがわかるかいっ?

「if文」と「#if」って似ていますよね。そのため、条件コンパイルはif文と大差がないのではないかと思ってしまう人もいるかもしれません。

でも、違うんです。条件コンパイルは、まさしくコードを「カットする」のです。

if文を使った場合

#include <stdio.h>

int main(void)
{

    if (0)
    {
        @ printf("Hello");
    }
    else
    {
        printf("World");
    }

    return 0;
}

条件コンパイルを使った場合

#include <stdio.h>

int main(void)
{

#if 0

	@ printf("Hello");

#else

	printf("World");
#endif

	return 0;
}

8行目にC言語が認識できない「@」の記号を入れておきました。この場合、ビルドするとif文を使用したケースでは次のビルドエラーが発生します。

error C2018: 文字 '0x40' は認識できません。

しかし、条件コンパイルではビルドエラーは発生しません。それは、まさしく対象部分をカットしているからなのです。

このようなことは、if文では行うことはできません。

Q:#ifを書き出す場所って常に1列目になってるけどなんで?

先生!条件コンパイルは、なぜ1列目から書くっすか?if文みたいにインデントを付けたらいいじゃないっすか?

切り絵師
切り絵師

そいつはだね、文化だよっ!江戸っ子つ~のは、文化を大切にするもんだよ。

条件コンパイルって行の先頭から書かれていることに皆さん気づいてますよね。

#include <stdio.h>

int main(void)
{
#if 0
	printf("Hello");
#endif
	return 0;
}

条件コンパイルを1列目から書くのって慣例なんです。

プリプロセッサによる#defineや#includeも1列目から記述しますね。プリプロセッサのキーワードは1列目から記述するのが慣例なんです。

#include <stdio.h>

int main(void)
{
    #if 0
        printf("Hello");
    #endif

    return 0;
}

よって、インデントを下げて書くことも可能なんですが、文化なので基本は先頭から書きます。

スポンサーリンク

課題:ifdef 条件コンパイルが学べたかを確認しよう

課題1

課題内容

次のプログラムがある。

#include <stdio.h>

int main(void)
{
    printf("Hello\n");

    printf("World\n");

    return 0;
}

次の条件コンパイルをプログラム内に配置し、「Hello」のみが表示されるようにせよ。

#if 1
#else
#endif

また、#if 0に切り替えた際に「World」のみが表示されるようにせよ。


出力期待結果

#if 1のとき

Hello

#if 0のとき

World

main.c

#include <stdio.h>

int main(void)
{
#if 1
    printf("Hello\n");
#else
    printf("World\n");
#endif

    return 0;
}
切り絵師
切り絵師

切り絵の基礎でやんすよ。皆さん、これが解けないようなら、切り絵の基礎をもう一度学び直すがようござんしょ。

次に学ぶべきカリキュラム