君はまだC言語のdefineのすべてを知らない【マクロ、プリプロセス】

398, 2022-01-27

目次

君はまだC言語のdefineのすべてを知らない

C言語はコンパイル型の言語で、ソースコードをコンパイラでコンパイルして実行ファイルを作ります。
コンパイルの処理では「プリプロセス」という処理があります。
これはマクロ置換などを行うコンパイルの前工程です。

defineはそのマクロ置換を行うプリプロセッサ指令の1つです。
この記事ではdefineのすべてをあなただけにお伝えします。

関連記事


プリプロセス指令のdefineで指定したトークンを、指定の値や式に置き換えることを「マクロ置換」と言います。
マクロ置換はプリプロセス処理で行われます。

ソースコードにベタ書きされた数字を「マジックナンバー」と言います。
マジックナンバーはその数字が「何を意味するのか」がわかりづらく、保守性が低いです。
そのためマクロを使って数字をトークンにしておく、というコーディングがよく行われます。

define指令で指定したトークンに数字や式を指定し、ソースコード中ではそのトークンを使うことで、ソースコードの可読性が向上し保守性も向上します。
またマクロ関数を使うことで複雑なマクロ置換も行うことができます。
プリプロセスはC言語のかなり強力な機能で、C言語のサブ言語と言っても良いほど奥が深いものです。

マクロ置換を覚えることでC言語によるプログラミングの質を向上させることができます。

defineの使い方

C言語のdefineは↓のような構造になっています。

#define トークン 置き換え後のコード

まずdefineの先頭にシャープ(#)を書きます。
これがプリプロセス指令の特徴です。
ついでdefineの次に置き換え対象のトークンを書きます。
その次に置き換え後のコード(値や式)を書きます。

実際のコードを見てみましょう。

#include <stdio.h>

#define EXPR (1 + 1)

int main(void) {
    int result = EXPR;

    printf("%d\n", result);
    // 2

    return 0;
}

↑のコードをコンパイルして実行すると「2」と出力されます。
defineの内容は↓になっています。

#define EXPR (1 + 1)

↑のdefineはトークン「EXPR」を式「(1 + 1)」で置き換えます。

つまり↓の式は、

    int result = EXPR;

↓のように置き換えられます。

    int result = (1 + 1);

このようにdefineを使うと特定のトークンを置き換えることが可能です。
defineのトークンは一般的には大文字で書かれることが多いです。
もちろん小文字でも書けます。
↓のようなトークンも有効です。

EXPR
EXPR_123
expr_123

大文字にする理由ですが、大文字にしておくとそのトークンがマクロだとわかるからです。
マクロは関数などとは違う振る舞いをするため、このように大文字にしておくことで他の機能と区別するということがよく行われます。

defineの注意点

defineは単純なトークンの置き換えにすぎません。
そのため、その動作に起因するバグがよく知られています。
このバグを防ぐための予防策は以下の通りです。

  • 置き換え後の式にはカッコを付ける

たとえば↓のdefineを見てください。

#define EXPR 1 + 1

↑のdefineでは式にカッコが付いていません。
このトークンを他の式の中で使うと↓のようになります。

    int result = 2 * EXPR;

プログラマーの意図では2 * EXPREXPRが先に計算されるはずでした。
つまり望む結果の式は「2 * (1 + 1)」です。
しかしEXPRは「1 + 1」に置換されるため、結果の式は「2 * 1 + 1」になります。
こうなると計算結果が変わってしまいます

このような計算ミスを予防するにはdefineの式にカッコを付けるクセを付けておきます。

#define EXPR (1 + 1)

複数行の置き換え

defineはトークンをコードに置き換えますが、コードは複数行書くことができます。

#include <stdio.h>

#define INFO printf("hello\n"); \
    printf("good\n"); \
    printf("world\n");

int main(void) {
    INFO;
    return 0;
}

↑のコードをコンパイルして実行すると↓の結果になります。

hello
good
world

defineで複数行のコードを書くときは、上記のように行の終端に「バックスラッシュ(\\)」を書きます。
こうするとプリプロセスが行末の改行を無視して次の行を読み込んで処理してくれます。

定義したトークンの削除

undef」を使うとdefineで定義したトークンを削除できます。

#define ONE 1
#undef ONE

defineはすでにトークンが定義されていた場合は警告を出すので、undefで予めトークンを削除しておくということも良く行われます。

#undef ONE
#define ONE 1

マクロ関数

defineでマクロ関数を定義できます。
マクロ関数の構造は↓になります。

#define マクロ関数名(トークン) コード 

マクロ関数はトークンをコードに置き換えるのは同じですが、その時にカッコの中のトークン(複数可)を使います。

#include <stdio.h>

#define DEF_VAR(type, var, value) type var = value

int main(void) {
    DEF_VAR(int, x, 1);

    printf("%d\n", x);

    return 0;
}

上記のコードをコンパイルして実行すると結果は「1」になります。
↑のマクロ関数DEF_VARでは、typeintに、varxに、value1に置き換えられます。
結果的に

DEF_VAR(int, x, 1)

というコードは

int x = 1;

と言うコードに置き換えられます。

マクロ関数を使うと普通のトークンより柔軟なマクロ置換が可能になります。
しかしマクロ関数を多用しすぎると奇怪なバグが生まれたりすることもあるのでデバッグが大変になり注意が必要です。
使う場合はほどほどにしておきましょう。

ifdef, ifndefによる分岐

ifdef」はトークンが定義されている時に実行されるプリプロセスのif文です。
たとえば↓のように使います。

#ifdef IS_ANIMAL
# define CAT "cat"
#endif

int main(void) {
    return 0;
}

ifdefを使う時は「endif」もセットで書くようにします。
↑のマクロは「IS_ANIMAL」が定義されていたら「CAT」を定義します。
↑のコードではIS_ANIMALは定義されていないのでCATも定義されません。

「ifdef」は↓のように書くこともできます。

#if defined(IS_ANIMAL)
# define CAT "cat"
#endif

ifndef」はトークンが定義されていない時に実行されるif文です。

#include <stdio.h>

#ifndef IS_ANIMAL
# define DOG "dog"
#endif

int main(void) {
    printf("%s\n", DOG);
    return 0;
}

↑のコードではIS_ANIMALは定義されていないのでDOGが定義されます。

「ifndef」は↓のように書くことも出来ます。

#if !defined(IS_ANIMAL)
# define DOG "dog"
#endif

おわりに

今回はC言語のdefineマクロについて解説しました。
マクロは慣れると便利に使えますが、厄介なバグを埋め込むこともあります。
用法容量を守って正しくお使いください。

(^ _ ^)

マクロですべてを置き換える

(・ v ・)

トークンは置き換えだ



この記事のアンケートを送信する