C言語のマクロはとても便利ですが、落とし穴も多くあります。このページではマクロの詳細について説明します。
関数形式マクロは、通常の関数と異なり、必ずインラインコードに展開されます。そのため、関数呼び出しのオーバーヘッドがないという特徴があります。関数に比べ、処理速度はやや速くなりますが、その反面、複数箇所で使用されるとその分コードサイズは大きくなります。また、マクロは関数ではありませんので、関数形式マクロへのアドレスというものもありません。
関数形式マクロの実引数には、どのような型の引数でも指定することができます。この特徴を活かして、様々な型の引数に対する同じ処理を1つのマクロで実現することも可能です。これは関数にはできないことです。関数では引数の型が限定されるためです。
最後になりましたが、マクロのとても重要な特徴に、よく理解しないまま利用すると思わぬ不具合を生む可能性がある、ということが挙げられます。これについては、マクロの注意点として後述します。
C言語には、あらかじめ定義されたマクロがあります。これらはC言語の規約(C99)で決められているもので、規約に準拠しているコンパイラならどのコンパイラでも使用可能です。この定義済みのマクロ名は#defineや#undefの対象にしてはいけません。
__DATE__は、ソースがコンパイルされた日付で、Mmm dd yyyyの形式を持つ文字列です。月の名前はasctime関数でつくられるものと同じになります。また、ddが10より小さい時は、最初の文字が空白で埋められます。
__TIME__は、ソースファイルがコンパイルされた時刻を表します。形式はhh:mm:ssの文字列となります。これはasctime関数で生成される時刻の形式と同じです。
__FILE__は、ソースファイル名を表す文字列となります。
__LINE__は、行番号を表す整数定数となります。文字列ではないことに注意してください。
__FILE__で表されるソースファイル名と__LINE__で表される行番号は、#line前処理指令を用いて変更することができます。
__STDC__は、C言語の規格に合致している処理系であるということを示す整数定数で、値は1です。
__STDC_HOSTED__は、処理系が規格に合致しているホスト処理系であることを示す整数定数で、値は1です。規格に合致していなければ値は0になります。
__STDC_VERSION__は、規格のバージョンを表す整数定数で、値は199901Lです。
関数形式マクロでは、引数を省略することができます。省略する際は、次の書式でマクロを定義します。
#define マクロ名(...) 置き換えられる処理
もしくは、
#define マクロ名(引数の並び, ...) 置き換えられる処理
簡単な例を見てみましょう。
/**
* @file MacroVaArgsSample.c
* @brief 関数形式マクロで引数を省略する例
*/
#include <stdio.h>
/**
* エラー出力
* 指定のデータを指定のフォーマットで標準エラー出力に出力する
* @param [in] ... エラー出力のフォーマットとデータ
* @retval 出力した文字数
* @retval 負の値ならエラー発生
*/
#define PRINT_ERROR(...) (fprintf(stderr, __VA_ARGS__))
int main(int argc, char *argv[])
{
PRINT_ERROR("%s %d¥n", "Error occurred. Code : ", -1);
return 0;
}
上記のように、省略記号には...を使います。省略された引数は、__VA_ARGS__で使用することができます。
省略記号を使用する場合は、呼び出し側で省略記号に相当する引数を必ず渡さなければなりません。また、__VA_ARGS__は、省略記号を用いた場合のみ使用可能です。
関数形式マクロでは、#演算子というものが使えます。#演算子を使うと渡された引数を文字列として、展開することができます。例を見てみましょう。
/**
* @file MacroHashSample.c
* @brief 関数形式マクロで#演算子を使用する例
*/
#include <stdio.h>
/**
* デバッグ出力
* 指定のデータをその変数名と共に出力する
* @param [in] a 出力対象データ
* @retval 出力した文字数
* @retval 負の値ならエラー発生
*/
#define DEBUG_PRINT(a) (printf("%s : %d¥n", #a, a))
int main(int argc, char *argv[])
{
int test_value = 100;
DEBUG_PRINT(test_value);
return 0;
}
このプログラムの実行結果は、
test_value : 100
となります。
このように、define定義の置換後の処理の部分で引数の前に#をつけて使用すると、その引数の値ではなく、変数名がマクロ展開されます。
#演算子を使用する場合、#の次の字句は必ず引数でなければなりません。また、変換に際には次のルールが適用されます。
(1)実引数の先頭と最後の空白類は取り除かれる。
(2)実引数の中の空白類は1つの空白文字にまとめられる。
(3)実引数が文字定数または文字列リテラルの場合、ダブルクォーテーション(")およびバックスラッシュ(\)の前にバックスラッシュを挿入する。(区切りのためのダブルクォーテーションも含めて)
(4)実引数が空ならば""(空の文字列)となる。
変換の結果が正しい単純文字列リテラルでない場合、その動作は未定義となります。
これらのルールを確認するためのサンプルが下記です。
/**
* @file MacroHashSample2.c
* @brief 関数形式マクロで#演算子を使用する例.その2
*/
#include <stdio.h>
/**
* デバッグ出力
* 指定のデータをその変数名と共に出力する
* @param [in] a 出力対象データ(整数型)
* @retval 出力した文字数
* @retval 負の値ならエラー発生
*/
#define DEBUG_PRINT(a) (printf("%s : %d¥n", #a, a))
/**
* デバッグ出力2
* 指定の文字列をその変数名と共に出力する
* @param [in] a 出力対象データ(文字列)
* @retval 出力した文字数
* @retval 負の値ならエラー発生
*/
#define DEBUG_PRINT_STR(a) (printf("%s : %s¥n", #a, a))
#define STR(a) #a
int main(int argc, char *argv[])
{
DEBUG_PRINT( 10 + 1 );
DEBUG_PRINT_STR("¥"abc¥"");
printf("%s¥n", STR());
return 0;
}
このプログラムを実行すると出力は次のようになります。
10 + 1 : 11
"\"abc\"" : "abc"
変換の際のルールが確認できると思います。
マクロにおいて、##演算子を使うとマクロ内の文字を連結することができます。##演算子はオブジェクト形式マクロでも関数形式マクロでも使用できます。例を見てみましょう。
/**
* @file MacroHashHashSample.c
* @brief 関数形式マクロで##演算子を使用する例
*/
#include <stdio.h>
#define TEST 10 ## 00
#define HASH_HASH_TEST(a) (printf("%d, %d¥n", a ## 1, a ## 2))
int main(int argc, char *argv[])
{
int data1 = 100;
int data2 = 200;
printf("%d¥n", TEST);
HASH_HASH_TEST(data);
return 0;
}
このプログラムの実行結果は次のようになります。
1000
100, 200
このように##演算子は、##の前後の字句を連結します。関数形式マクロの場合で、##の前後の字句が引数であった場合、実引数の変数名が連結の対象となります。##演算子は、先頭または終わりにおいてはいけません。
マクロ置換が行われた結果の中にさらにマクロ名が含まれることがあります。そのような場合には、マクロの再置き換えが行われます。その時には次のような処理が行われます。
(1)マクロ置換を行う。オブジェクト形式マクロなら単純に置き換える。関数形式マクロなら、パラメーターを実引数に置き換え、さらに#演算子、##演算子の処理を行う。
(2)置換結果に対して、さらにマクロ置換を行う。ただし、置換中のものと同じマクロ名を検出した場合は置換しない(無限に処理が繰り返されるのを防ぐため)。
(3)これを置き換えることのできるマクロ名がなくなるまで繰り返す。
例を見てみましょう。
#define A(a) #a a A(1) B(2) A ## a
#define B(a) C(a)
#define C A
A(A(3));
上記のように定義されていた場合、A(A(3));は、次のように展開されていきます。
A(A(3));
これが、最初の状態です。ここから、引数の置き換えと、#演算子と##演算子の処理が行われ、次のようになります。
"A(3)" A(3) A(1) B(2) AA(3);
A(3)が展開されて、次のようになります。A(1)は展開されません。
"A(3)" "3" 3 A(1) B(2) A3 A(1) B(2) AA(3);
この状態から、BがCに置き換わります。
"A(3)" "3" 3 A(1) C(2) A3 A(1) C(2) AA(3);
そして、CがAに置き換わります。
"A(3)" "3" 3 A(1) A(2) A3 A(1) A(2) AA(3);
これが置換結果となります。A(2)は展開されません。
マクロの置換結果を確認する時には、gccでプリプロセスのみを行うオプションを指定してコンパイルすると便利です。関連するオプションには次のようなものがあります。
-E … プリプロセスだけを行う。結果は標準出力に出力される。
-C … コメントを残すようにする。-Eとともに使用する。
-P … #line行を生成しない。-Eとともに使用する。