Best of show

最優秀賞

受賞者:Jack Applin

引用元:https://www.ioccc.org/1988/applin.c

審査員・作者による説明:https://github.com/ioccc-src/winner/blob/main/1988/applin.hint

動作

Cプリプロセッサによる計算で素数を列挙する。 ビルドオプションがいっぱい必要。

$ gcc -E '-DM=#include "z.c"' '-DR=#include' '-DF=#if' '-DI=#ifdef' '-DL=#else' '-DE=#endif' '-DN=#ifndef' '-DD=#define' '-DU=#undef' applin.c > z.c

$ gcc -E z.c > large.c

$ gcc -o applin large.c

$ ./applin
2
3
5
7
11
13
17
19
23
29
31
...
439
443
449

解説

第1のポイントは、自分自身を再帰的に#includeすること。

#ifndef z
#define z
#include "z.c"
#include "z.c"
#include "z.c"
...
#include "z.c"
#endif

これにより、このコードを何回もプリプロセスすることになる。

第2のポイントは、次のギミックが肝となっている。

#ifdef a
#undef a
#else
#define a
#endif

これは、マクロaが定義されていたら#undefし、定義されていなかったら#defineする。 よって、このコードをプリプロセスのたびにマクロaの定義状態が反転するようになる。 これで1ビットを表現する。

こういうマクロをいくつも入れ子にすることで、カウンタを構築している。 このカウンタからvv=1,3,5,…,1023とff=1,3,5,…31を作り、各vvについてどのffでも割り切れなかったらprintf文を出す。

除算の判定はプリプロセッサの整数演算によって普通に行える(この点は[[2004/vik2]]によってさらにストイックに改良がなされる)。

最終的な出力であるlarge.cをgrep -v "#"したら、次のような感じのコードが観察できる。 インデントは適当に入れた。

main(){
  printf("%d\n", 2);
  printf("%d\n", (0 +0 +0 +0 +0 +0 +0 +0 +(1<<1)+1));
  printf("%d\n", (0 +0 +0 +0 +0 +0 +0 +(1<<2)+0 +1));
  printf("%d\n", (0 +0 +0 +0 +0 +0 +0 +(1<<2)+(1<<1)+1));
  printf("%d\n", (0 +0 +0 +0 +0 +0 +(1<<3)+0 +(1<<1)+1));
  printf("%d\n", (0 +0 +0 +0 +0 +0 +(1<<3)+(1<<2)+0 +1));
  printf("%d\n", (0 +0 +0 +0 +0 +(1<<4)+0 +0 +0 +1));
  printf("%d\n", (0 +0 +0 +0 +0 +(1<<4)+0 +0 +(1<<1)+1));
  ...
}

初のBest of Show受賞作。 Cプリプロセッサ、つまりコンパイル時に非自明な計算を行う、初期の事例だと思われる。 applin.hintでは、当時の計算機でプリプロセスに75分かかったとか、プリプロセッサのバグが見つかったとか、審査員の興奮が伺える。 残念ながら、現代のコンパイラでは一瞬で処理できてしまうので、情緒が足りない。