C で lisp のwith系マクロごっこ
色々なパラダイムの言語を噛った後に、c をやるのは面白いかもしれない。
例によってcygwin + gccで確認してます。引数を取るマクロ(#define)は、便利なんですが、予想以上でした。プリプロセッサが処理する時は単なる文字列として扱われるという認識です。「マクロの引数に指定できるものは何だろう?」というのが元々なんですが、文字列だから何でもいいと思います。で、色々と弄っていると、複数の文でさえ、引数として渡せることが判りました!?。(cygwin+gccだけで確認しました。念のため)
lispのマクロ定義の引数でいうところの (defmacro name (&body body) …) のbodyと同じような機能を持っていることになります。
複数の文をマクロの引数に指定してみる
#include <stdio.h> #include <stdlib.h> #define loop(body) while(1){body} int main(int argc, char** argv) { int i = 0; loop( if (i > 5) break; printf("%d\n", i++); ); return 0; }
実行結果
$ gcc -Wall loop.c $ ./a.exe 01 2 3 4 5
こんなことが、できてしまいました。ちょっと説明すると、「if (i > 10) break; printf(“%d\n”, i++);」という複数の文が、loopマクロのbodyという引数に指定されて、whileに展開されました。これって、lispのマクロじゃん?色々な点でlispには及ばないけど、思っていた以上に*1強力でした。syntaxを追加できると言っても過言ではないような。。。
lispのwith系マクロごっこ
という流れで、もうちょっと変なのを定義してみました。ファイルハンドルの後始末をしてくれるwith_file_read_lineマクロを書いてみました。その実行例として、catコマンドみたいな処理をしてみました。
#include <stdio.h> #include <stdlib.h> #define with_file_read_line(file_name, line, length, body) \ FILE *fp; \ if ((fp = fopen(file_name, "r")) == NULL) { \ fprintf(stderr, "file open error!!\n"); \ exit(EXIT_FAILURE); \ } \ while (fgets(line, length, fp) != NULL) { \ body \ } \ fclose(fp); \ printf("closed.\n"); int main(int argc, char** argv) { char line[100]; int row_no = 1; with_file_read_line("with.c", line, 100, printf("%5d: ", row_no++); printf("%s", line); ); return 0; }
実行結果
$ gcc -Wall with.c
$ ./a.exe
1: #include <stdio.h>
2: #include <stdlib.h>
3:
4: #define with_file_read_line(file_name, line, length, body) \
5: FILE *fp; \
6: if ((fp = fopen(file_name, "r")) == NULL) { \
7: fprintf(stderr, "file open error!!\n"); \
8: exit(EXIT_FAILURE); \
...(ry
closed.
closed.と表示されたので、with_file_read_lineマクロを抜ける前に、ちゃんとファイルを閉じてくれたようです。
ところで、マクロの引数に、複数の文を指定できるのは、gccの拡張機能なんだろうか?
お遊びとしては、十分楽しかった。
*1:cのマクロに抱いていた偏見のせいで