GCCのプリプロセッサの動きをみて遊ぶ

ruby-libvirtソースコードを読んでいたら、何やら怪しげなマクロが。

  67 #define generic_get(kind, v)                                            \
  68     do {                                                                \
  69         vir##kind##Ptr ptr;                                             \
  70         Data_Get_Struct(v, vir##kind, ptr);                             \
  71         if (!ptr)                                                       \
  72             rb_raise(rb_eArgError, #kind " has been freed");            \
  73         return ptr;                                                     \
  74     } while (0);
  75 

シャープだと!しかも2つだと!見たことないぞ!
というわけで引数マクロについてちょっと実験してみました。

基本的な引数付きマクロ

まずは基本的な引数マクロ。

  1 #include <stdio.h>
  2 
  3 #define test_puts(hoge)             \
  4     do {                            \
  5         hoge("inside hoge");        \
  6     } while (0);
  7       
  8 main()
  9 {   
 10     test_puts(puts);
 11 }

プリプロセッシング結果*1

$ gcc -E a.c
main()
{
 do { puts("inside hoge"); } while (0);;
}

引数hogeがputsに展開されている様子がわかります。

引数付きマクロの引数をマクロ中テンプレートとくっつける

##を使うと、「引数とマクロ内のソースコードとくっつける」することができます。

  1 #include <stdio.h>
  2 
  3 #define test_puts(hoge)                 \
  4     do {                                \
  5         p##hoge##f("inside hoge");      \
  6     } while (0);
  7 
  8 main()
  9 {
 10     test_puts(rint);
 11 }

プリプロセッシング結果。

$ gcc -E a.c
main()
{
 do { printf("inside hoge"); } while (0);;
}

うおおお!!これはすごい。

渡した引数を"(ダブルクオート)でくくってCの文字列に変換

個人的にはこれが一番変態的だと思う。
マクロ内では、#で与えられた変数は"(ダブルクオート)でくくられてCの文字列になります。

  1 #include <stdio.h>
  2 
  3 #define test_puts(hoge)             \
  4     do {                            \
  5         puts(#hoge);        \
  6     } while (0);
  7 
  8 main()
  9 {
 10     test_puts(aiya-);
 11 }
$ gcc -E a.c
main()
{
 do { puts("aiya-"); } while (0);;
}

うひょー!これはすごい。まだまだ知らないことが多いなぁ。
ruby-libvirtでは、このテクニックを使ってコード量を削減しています。nice redhat.

*1:実際は#includeが展開されている様子が表示されますが、今回は無視します。