パラメータ付きマクロ(C++)
プリプロセッサ:インデックス への移動
パラメータ付きマクロを定義する構文を、次に示します。
#define macro_identifier(<arg_list>) <token_sequence>
macro_identifier
と左かっこ (
を、空白で区切ることはできません。オプションの arg_list
は、C 関数の引数リストのように、コンマで区切った一連の識別子です。ただし、引数リスト内の内部かっこ内のコンマはすべて引数の一部として扱われ、引数の区切り文字としては扱われません。コンマで区切られた識別子それぞれが、仮引数、つまりプレースホルダとしての役割を果たします。
これらのマクロは、次のように記述して
macro_identifier<whitespace>(<actual_arg_list>)
後続のソース コードで呼び出します。この構文は、関数呼び出しの構文と同じです。ただし、重要な意味上の相違点、副作用、および潜在的な落とし穴がいくつかあります。
オプションの actual_arg_list
には、#define 行の仮の arg_list
と同じ数の、コンマで区切られたトークン シーケンス(実引数)を設定する必要があります。仮引数それぞれに対して、実引数が必要です。これら 2 つのリストで引数の数が異なると、エラーが発生します。
マクロ呼び出しでは、2 つの置換が行われます。はじめに、macro_identifier
とかっこで囲まれた引数が、token_sequence
に置換されます。次に、token_sequence
内の仮引数が、対応する actual_arg_list
内の実引数に置換されます。
単純なマクロ定義では、再検索が行われ、展開の対象となる組み込みのマクロ識別子が検出されます。
かっことコンマをネストする
actual_arg_list
では、正しく対応しているかっこをネストできます。また、かっこ内のコンマは引数の区切文字として扱われません。
#define ERRMSG(x, str) printf("Error: %d \n%s", x, str) #define SUM(x,y) ((x) + (y)) : ERRMSG(2, "Press Enter, then ESC"); /*expands to: printf("Error: %d \n%s", 2, "Press Enter, then ESC"); */ return SUM(f(i, j), g(k, l)); /*expands to: return ( (f(i, j)) + (g(k, l)) ); */
副作用とその他の危険性
関数呼び出しとマクロ呼び出しは似ているので、その違いがあいまいになることがよくあります。マクロ呼び出しには組み込み型のチェック機能がないため、仮引数と実引数のデータ型が不一致でもそれを直接示す警告が検出されずに、奇妙なデバッグしづらい状況に陥る可能性があります。マクロ呼び出しは、実引数が複数回評価される場合は特に、予期しない副作用を発生させる可能性があります。
次の例で、CUBE
と cube
を比較してみてください。
int cube(int x) { return x*x*x; } #define CUBE(x) ( (x) * (x) * (x) ) ... int b = 0, a = 3; b = cube(a++); /* cube() is passed actual argument a = 3; so b = 27; now a = 4 */ a = 3; b = CUBE(a++); /* expands as ( (a++) * (a++) * (a++) ); again b = 27 but now a = 6 */
# 演算子で実引数を文字列に変換する
マクロの仮引数の前に # 記号を記述すると、置換後に実引数を文字列に変換できます。
次に宣言例を示します。
#define TRACE(flag) printf(#flag "=%d\n", flag)
次のコードは、
int highval = 1024; TRACE(highval);
次のように変換され、
int highval = 1024; printf("highval" "=%d\n", highval);
続いて次のように扱われます。
int highval = 1024; printf("highval=%d\n", highval);