[C] マクロトークンの文字列化

2017年4月3日月曜日

C

マクロの文字列化演算子

Cのマクロで数値定数が存在する場合にログに数値のみではなく、定数名を出力したい場合がある。
こういうときはマクロの文字列化演算子(#)を使う。
#define STR(S) #S

#define ORANGE 1
#define APPLE  2
#define GRAPE  3

...
 printf(STR(ORANGE) "\n");
上記の例だと、以下のようにマクロが展開され、標準出力に ORANGE と出力される。
 printf("ORANGE" "\n");

文字列化演算子は引数ありのマクロ(関数マクロ)の引数に作用するため、STR(S) のような関数マクロを定義して使う必要がある。

じゃあ、マクロ名と値を含んだメッセージを作りたい場合はどうしようか。

マクロ名と値を両方出力しよう

例えば、MESSAGE(APPLE) としたら、"APPLE:2" となって欲しいとしよう。
こんなときは以下のように文字列化するマクロに関数マクロを重ねよう。
#define STR(S) #S
#define MESSAGE(S) #S ":" STR(S)

#define ORANGE 1
#define APPLE  2
#define GRAPE  3

...
 printf(MESSAGE(APPLE) "\n");
こうすれば、以下のように展開されて、APPLE:2 と出力される。
 printf("APPLE" ":" "2" "\n");

マクロの展開順序

なんでこんな風になるかっていうと、関数マクロの展開順序として、実引数の展開の前に文字列化演算子が作用して、その後にまだマクロが残っていれば、さらにマクロが展開されるからだ。

なので、さっきのMESSAGEマクロの展開を順を追ってみると、以下になる。

[1] MESSAGE(APPLE)
[2] #APPLE ":" STR(APPLE)
[3] "APPLE" ":" STR(APPLE)
[4] "APPLE" ":" STR(2)
[5] "APPLE" ":" #2
[6] "APPLE" ":" "2"

引数として関数マクロを渡してみる

ちょっと別の方向から見てみよう。
今度は関数マクロの定義に関数マクロを入れ込むのではなく、引数として関数マクロを渡してみよう。
#define I0(S) S
#define I1(S) #S
#define I2(S) S

#define J0(S) #S
#define J1(S) S
#define J2(S) S

#define AAA 12345
#define BBB AAA

I2(I1(I0(BBB)))
J2(J1(J0(BBB)))
これにプリプロセスを行うと、以下のようになる。
"I0(BBB)"
"BBB"

さっきと違って、文字列化演算子の前段に関数マクロが存在するのに今回は BBB が置換されなかった!
試しに J0 を抜くとちゃんと BBB が 12345 に展開される。
J2(J1(BBB))
12345

基本的なルールとして、Cの式は内側から評価されていくけど、マクロは外側からトークンが置換されていく。
マクロの展開後にマクロがある場合は、一度展開されたマクロと同名でない限り、さらに置換される。
同名のマクロが置換されないのはマクロが再帰的定義になってしまうのを防ぐためだね。

関数マクロと引数を別々に渡してみる

今度は関数マクロと引数を別々に渡してみよう。
#define K0(S) #S
#define K1(K, S) K(S)

K1(K0, BBB)
"12345"
今度はBBBが展開されて、"12345"になったね。

これはマクロが文字列ではなく、あくまでトークンを置換していると考えると納得がいく。

#define K1(K, S) K(S)
K1(K0, BBB)
の場合は K と S は別々のトークンであるため、K1 の展開時にそれぞれ独立して置換される。

#define J2(S) S
J2(J1(J0(BBB)))
の場合は S は1つのトークンであるため、J1(J0(BBB)) の J1,J0,BBB は独立して置換されず、J1(J0(BBB)) という塊でマクロ展開される。