Caution
お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。
関数マクロ(#define)
引数を取るマクロです。関数のように使えますが、コンパイル前にプリプロセッサが単純なテキスト置換を行います。インライン展開されるため関数呼び出しのオーバーヘッドがなく、型に依存しない汎用的なコードが書けます。
構文
// 引数なしの定数マクロです。
#define 定数名 値
// 引数を取る関数マクロです(引数と全体を括弧で囲みます)。
#define マクロ名(引数1, 引数2) ((引数1) 演算 (引数2))
// 複数行マクロはバックスラッシュで行を継続します。
#define マクロ名(x) \
do { \
処理; \
} while (0)
// 文字列化演算子(# )です。引数を文字列リテラルに変換します。
#define STRINGIFY(x) #x
// トークン連結演算子(##)です。2つのトークンを結合します。
#define CONCAT(a, b) a##b
関数マクロの記法と注意点
| 記法・演算子 | 概要 |
|---|---|
| 引数を括弧で囲む | 『#define SQUARE(x) ((x) * (x))』のように引数を括弧で囲みます。展開時の演算子優先順位による誤動作を防ぎます。 |
| 全体を括弧で囲む | マクロ全体を括弧で囲むことで、式の中に埋め込んだときの意図しない結合を防ぎます。 |
| do { } while (0) | 複数文マクロを安全に書くイディオムです。if文の本体に使われても正しく動作します。 |
| # (文字列化) | マクロ引数をそのままダブルクォートで囲んだ文字列リテラルに変換します。 |
| ## (トークン連結) | 2つのトークンを結合して1つの識別子にします。 |
サンプルコード
#include <stdio.h>
// よくある関数マクロの例です。
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
#define ABS(x) ((x) >= 0 ? (x) : -(x))
// デバッグ用のマクロです(# で変数名を文字列化します)。
#define PRINT_INT(x) printf(#x " = %d\n", (x))
// do-while(0) を使った複数文マクロです。
#define SWAP(type, a, b) \
do { \
type _tmp = (a); \
(a) = (b); \
(b) = _tmp; \
} while (0)
// トークン連結で識別子を動的に生成します。
#define MAKE_VAR(name, num) name##num
int main(void) {
int x = 5, y = 3;
printf("MAX(%d, %d) = %d\n", x, y, MAX(x, y)); // 『MAX(5, 3) = 5』と出力されます。
printf("MIN(%d, %d) = %d\n", x, y, MIN(x, y)); // 『MIN(5, 3) = 3』と出力されます。
printf("SQUARE(%d) = %d\n", x, SQUARE(x)); // 『SQUARE(5) = 25』と出力されます。
printf("ABS(-7) = %d\n", ABS(-7)); // 『ABS(-7) = 7』と出力されます。
// # 演算子でデバッグ出力します。
int score = 98;
PRINT_INT(score); // 『score = 98』と出力されます。
// SWAP マクロで値を交換します。
printf("交換前: x=%d, y=%d\n", x, y);
SWAP(int, x, y);
printf("交換後: x=%d, y=%d\n", x, y); // 『x=3, y=5』と出力されます。
// ## 演算子の例です。
int MAKE_VAR(val, 1) = 100; // val1 = 100 に展開されます。
printf("val1 = %d\n", MAKE_VAR(val, 1)); // 『val1 = 100』と出力されます。
// 副作用のある式をマクロに渡すと意図しない動作になります。
// int a = 3;
// printf("%d\n", SQUARE(a++)); // a が2回インクリメントされて危険です。
return 0;
}
概要
関数マクロはプリプロセッサによるテキスト置換のため、引数に副作用のある式(インクリメントなど)を渡すと意図しない動作になります。『SQUARE(a++)』は『((a++) * (a++))』に展開され、インクリメントが2回起きます。C99以降では同じ目的に『inline』関数や型汎用の『_Generic』を使うことを検討してください。
引数を括弧で囲まないと演算子優先順位の問題が起きます。例えば『#define DOUBLE(x) x * 2』の場合、『DOUBLE(1 + 2)』は『1 + 2 * 2 = 5』と展開されます。引数は必ず括弧で囲んでください。
定数マクロについては『#include / #define(定数)』を、条件付きコンパイルについては『#ifdef / #ifndef』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。