m5knt

底が浅い男がなんとはなしに...

フォーマットを扱うマクロを適切に使い分けてくれないにょろ

C言語で文字列を整形する為に printf 等を利用することが多いわけなんですが printf 的なものをマクロ LOGF(...) でくるむと文字列表示期待で LOGF("%s", "A") ではなく LOGF("A") としがちです、%が混じってなければ安全なんですがこのような形を続けられるといつの間にか % がフォーマット文字列に追加され引数が増えなことが多々有ります。 (多くは出力文字列が壊れる程度だけど制御コードが発動したり %n がきたりすると悲しい目に遭います)

人がやることなんで間違うのはしょうが無いということで F がフォーマット指定に見えないなら出来るだけ回避するように考えてみました。

まずはコンマがあるか調べるマクロを用意します

#define HAS_COMMA(...) HAS_COMMA_(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0)
#define HAS_COMMA_(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19,...) a19

参考 https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/

(ゲーム屋なので 4x4 以上にしました)

トークンを連結するマクロを用意します

#define JOIN(a, b) JOIN2(a, b)
#define JOIN2(a, b) a ## b

あとはカンマが有ればマクロを切り替える感じで

#define LOG(...) JOIN(LOG_, HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define LOG_0(str) printf("%s", str)
#define LOG_1(...) printf(__VA_ARGS__)

期待通り展開されるか確認します

a.cpp

LOG("%s\n");
LOG("%s\n", "abc");

g++ -E a.cpp

printf("%s", "%s\n");
printf("%s\n", "abc");

これで文字列がフォーマットと解釈される危険が少なくなりましたよ~