地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【C】マクロ

拝啓

カーネルソース読んでるとよく出てくる「do {} while (0);」 これが何なのか気になったので調べてみました。

torvalds/linux

以下swap関数のようなイメージ 至るところでdo {} while(0)が存在

/**
 * swap - swap values of @a and @b
 * @a: first value
 * @b: second value
 */
#define swap(a, b) \
    do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

C言語におけるマクロとは

プログラムの中の文字列を予め定義した規則にしたがって置換する機能のこと マクロは「#define」というプリプロセッサ指令により定義される

プリプロセッサとは

一般にある処理を行うソフトウェアに対して、 データ入力やデータ整形などの準備的な処理を行うソフトウェアのことである。 特にコンパイラに対して使うことが多い

プリプロセッサ一覧

C言語には以下膿瘍名プリプロセッサ指令がある

|種類|説明| ---------|---- define |マクロを定義 ifdef |シンボルが定義されているときに定義する(類似で#infdef、#endif) if |式が真のときに実行する include |ヘッダファイルのインクルード error |コンパイラにエラーを発生させる warning |コンパイラに警告を発生させる prgma |マシンやOS固有の機能をサポートする

今回ポイントとなるのは「#define」です。

#defineについて

defineで定義されるマクロいくつか種類があります。 その中でも今回は「関数形式マクロについて取り上げます」

  • オブジェクト形式マクロ
  • 関数形式マクロ

関数形式マクロとは、マクロ置換を行う時に引数を取ることができ、あたかも関数のように使用できるマクロです。 関数形式マクロは、次の書式で定義します。

#define マクロ名(引数の並び) 置き換えられる処理

上記を使った簡単なサンプルプログラム。 数字を入れ替えるマクロを定義し、mainで実行。

#include<stdio.h>

# 引数2つを入れ替えるマクロ
#define swap(a, b) \
        do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

int main(int argc, char** argv) {
    int x = 10;
    int y = 20;

    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);
    SWAP(x, y);
    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    return 0;
}

実行結果は下記のようになります。

$ gcc main.c -o main
$ ./main
(x, y) = (10, 20)
(x, y) = (20, 10)

上記のような実装では特に問題はない事が分かりました。 自分で書いたマクロを自分で利用するだけなので今回は問題なし。 じゃあ何故do{}while(0)なんて入れてるんだろうってことで調べてみると 以下2点理由があった。

  • 処理の打ち切りテクニック
  • マクロを使用する場所に依存するエラーを防ぐ

今回は2点目の使用場所に依存するエラーを防ぐ目的がある模様。

具体的にエラーが出る使用方法は下記

#include<stdio.h>

#define swap(a, b) \
        typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;

int main(int argc, char** argv) {
    int x = 10;
    int y = 20;

    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    if (x > 20) SWAP(x, y);
    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    return 0;
}

マクロが展開された状態のソースは下記となり

#include<stdio.h>

#define swap(a, b) \
        typeof(a) __tmp = (a); (a) = (b); (b) = __tmp;

int main(int argc, char** argv) {
    int x = 10;
    int y = 20;

    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    if (x > 20) typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; //ここでエラー
    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    return 0;
}

エラーとなる原因はマクロ展開後のif文が「if (x > 20) typeof(a) __tmp = (a);」で完結しており 以降の行で出てくるa,bが未定義といったエラー

do{}while(0)を使用することで使用場所に依存しないマクロの定義が可能となる。

#include<stdio.h>

#define swap(a, b) \
        do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)

int main(int argc, char** argv) {
    int x = 10;
    int y = 20;

    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    if (x > 20) do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0)
    fprintf(stdout, "(x, y) = (%d, %d)\n", x, y);

    return 0;
}