TOP >> マニアックなプログラミング
トリッキーコードネット トリッキーなコード

【芸術的な凄いプログラミング】IOCCC作品のコード解説3:main再帰 / 関数ポインタでの2進数カウント

#define p struct c #define q struct b #define h a->a #define i a->b #define e i->c #define o a=(*b->a)(b->b,b->c) #define s return a;}q* #define n (d,b)p*b;{q*a;p*c; #define z(t)(t*)malloc(sizeof(t)) q{int a;p{q*(*a)();int b;p*c;}*b;};q*u n a=z(q);h=d;i=z(p);i->a=u;i->b=d+1;s v n c=b;do o,b=i;while(!(h%d));i=c;i->a=v;i->b=d;e=b;s w n o;c=i;i=b;i->a=w;e=z(p);e->a=v;e->b=h;e->c=c;s t n for(;;)o,main(-h),b=i;}main(b){p*a;if(b>0)a=z(p),h=w,a->c=z(p),a->c->a=u, a->c->b=2,t(0,a);putchar(b?main(b/2),-b%2+'0':10);}
↑のコードは、1985年のIOCCCグランプリ作品です。 このコードをコンパイルして実行すると、以下の様に延々と2進数がカウントされます。^^;) IOCCC作品解説:2進数カウントプログラミング (※ 実行時にコマンドラインパラメータは渡しません。) それでは、コードを解説していきます。 まず、冒頭のコードは #defineによる定義が多用されている為、そのままではコードが非常に分けワカメちゃんです orz その為、初めにプリプロセッサの実行結果を取得します。 (VC++によるプリプロセッサの実行結果取得方法はコチラを参照して下さい^^;) プリプロセッサの実行結果は以下のとおりです↓↓
struct b{int a;struct c{struct b*(*a)();int b;struct c*c;}*b;};struct b*u (d,b)struct c*b;{struct b*a;struct c*c; a=(struct b*)malloc(sizeof(struct b));a->a=d;a->b=(struct c*)malloc(sizeof(struct c));a->b->a=u;a->b->b=d+1;return a;}struct b* v (d,b)struct c*b;{struct b*a;struct c*c; c=b;do a=(*b->a)(b->b,b->c),b=a->b;while(!(a->a%d));a->b=c;a->b->a=v;a->b->b=d;a->b->c=b;return a;}struct b* w (d,b)struct c*b;{struct b*a;struct c*c; a=(*b->a)(b->b,b->c);c=a->b;a->b=b;a->b->a=w;a->b->c=(struct c*)malloc(sizeof(struct c));a->b->c->a=v;a->b->c->b=a->a;a->b->c->c=c;return a;}struct b* t (d,b)struct c*b;{struct b*a;struct c*c; for(;;)a=(*b->a)(b->b,b->c),main(-a->a),b=a->b;}main(b){struct c*a;if(b>0)a=(struct c*)malloc(sizeof(struct c)),a->a=w,a->c=(struct c*)malloc(sizeof(struct c)),a->c->a=u, a->c->b=2,t(0,a);putchar(b?main(b/2),-b%2+'0':10);}
そして、上記コードに、適当なインデント・スペースを入れて読みやすくした物が以下のコードです。↓↓
1 : struct b{ 2 : int a; 3 : struct c{ 4 : struct b * (*a)(); 5 : int b; 6 : struct c *c; 7 : } 8 : *b; 9 : }; 10 : 11 : struct b* u (d,b) struct c* b; 12 : { 13 : struct b* a; 14 : struct c* c; 15 : a = (struct b*)malloc(sizeof(struct b)); 16 : a -> a = d; 17 : a -> b = (struct c*)malloc(sizeof(struct c)); 18 : a -> b -> a = u; 19 : a -> b -> b = d + 1; 20 : return a; 21 : } 22 : 23 : struct b* v (d,b) struct c* b; 24 : { 25 : struct b* a; 26 : struct c* c; 27 : c = b; 28 : 29 : do 30 : a = (*b -> a)(b -> b,b -> c),b = a -> b; 31 : while (! (a -> a % d)); 32 : 33 : a -> b = c; 34 : a -> b -> a = v; 35 : a -> b -> b = d; 36 : a -> b -> c = b; 37 : 38 : return a; 39 : } 40 : 41 : struct b* w (d,b) struct c* b; 42 : { 43 : struct b* a; 44 : struct c* c; 45 : 46 : a = (*b -> a)(b -> b,b -> c); 47 : c = a -> b; 48 : a -> b = b; 49 : a -> b -> a = w; 50 : a -> b -> c = (struct c*)malloc(sizeof(struct c)); 51 : a -> b -> c -> a = v; 52 : a -> b -> c -> b = a -> a; 53 : a -> b -> c -> c = c; 54 : return a; 55 : } 56 : 57 : struct b* t (d,b)struct c* b; 58 : { 59 : struct b* a; 60 : struct c* c; 61 : 62 : for (;;) 63 : a = (*b -> a)(b -> b,b -> c),main(-a -> a),b = a -> b; 64 : } 65 : 66 : main(b) 67 : { 68 : struct c* a; 69 : 70 : if (b > 0) 71 : a = (struct c*)malloc(sizeof(struct c)), 72 : a -> a = w, 73 : a -> c = (struct c*)malloc(sizeof(struct c)), 74 : a -> c -> a = u, 75 : a -> c -> b = 2, 76 : t(0,a); 77 : 78 : putchar (b ? main(b/2), -b%2 + '0' : 10); 79 : }
冒頭のコードよりは幾分読みやすくなったのですが、変数名にわざわざトリッキーなものを使用している為、適切に変換します。 まず、随所で使われている構造体名を、tagXXXに置換します (ex struct a ⇒ struct tagA)。 そして、構造体名と変数名があっていないもの (ex tagAの変数なのに bという変数名がついていたり)は、適切に変数名を置換します。 さらに、int型変数の a, b, d は、n1, n2, i に変数名を置換し、関数 u, v, w, tも適切に関数名を置換します。 また、4行目の struct tagB * (*a)(); での 「a」は、「struct tagBポインタを返す関数のポインタ」の為、変数名を「a」⇒ 「getTagB」にします。 11行目の 「struct tagB* u (d,b) struct tagC* b;」 は 「struct tagB* u (int d, struct tagC *b)」と等価の為、コードを修正します。 (23, 41, 57行目も同様に修正します。) 14行目と60行目の変数定義は、必要ないためコメントアウトします。 ついでに、「'malloc','main','putchar'が定義されていない」とコンパイラに怒られる為、 コード先頭部分にて、インクルードファイルの宣言と、main関数のプロトタイプ宣言を行います。 これらの修正を反映したコードは、以下の様になります。 (※ 修正前のコードと比較する為、先頭の宣言部分は行数表示から除外してあります。)
#include <stdio.h> #include <stdlib.h> int main(int); 1 : struct tagB{ 2 : int n1; 3 : struct tagC{ 4 : struct tagB * (*getTagB)(); 5 : int n2; 6 : struct tagC *c; 7 : } 8 : *c; 9 : }; 10 : 11 : struct tagB* functionU (int i, struct tagC* paramC) 12 : { 13 : struct tagB* b; 14 : // struct tagC* c; 15 : b = (struct tagB*)malloc(sizeof(struct tagB)); 16 : b -> n1 = i; 17 : b -> c = (struct tagC*)malloc(sizeof(struct tagC)); 18 : b -> c -> getTagB = functionU; 19 : b -> c -> n2 = i + 1; 20 : return b; 21 : } 22 : 23 : struct tagB* functionV (int i, struct tagC* paramC) 24 : { 25 : struct tagB* b; 26 : struct tagC* c; 27 : c = paramC; 28 : 29 : do 30 : b = (*paramC -> getTagB)(paramC -> n2, paramC -> c), paramC = b -> c; 31 : while (! (b -> n1 % i)); 32 : 33 : b -> c = c; 34 : b -> c -> getTagB = functionV; 35 : b -> c -> n2 = i; 36 : b -> c -> c = paramC; 37 : 38 : return b; 39 : } 40 : 41 : struct tagB* functionW (int i, struct tagC* paramC) 42 : { 43 : struct tagB* b; 44 : struct tagC* c; 45 : 46 : b = (*paramC -> getTagB)(paramC -> n2, paramC -> c); 47 : c = b -> c; 48 : b -> c = paramC; 49 : b -> c -> getTagB = functionW; 50 : b -> c -> c = (struct tagC*)malloc(sizeof(struct tagC)); 51 : b -> c -> c -> getTagB = functionV; 52 : b -> c -> c -> n2 = b -> n1; 53 : b -> c -> c -> c = c; 54 : return b; 55 : } 56 : 57 : struct tagB* functionT (int i, struct tagC* paramC) 58 : { 59 : struct tagB* b; 60 : // struct tagC* c; 61 : 62 : for (;;) 63 : b = (*paramC -> getTagB)(paramC -> n2, paramC -> c), main(-b -> n1), paramC = b -> c; 64 : } 65 : 66 : main(i) 67 : { 68 : struct tagC* c; 69 : 70 : 71 : if (i > 0) 72 : c = (struct tagC*)malloc(sizeof(struct tagC)), 73 : c -> getTagB = functionW, 74 : c -> c = (struct tagC*)malloc(sizeof(struct tagC)), 75 : c -> c -> getTagB = functionU, 76 : c -> c -> n2 = 2, 77 : functionT(0, c); 78 : 79 : putchar (i ? main(i/2), -i%2 + '0' : 10); 80 : }
ではいよいよコードを読んで行きたいと思います。щ(゚Д゚щ)カモォォォン パッと見でも分かる様に、このコードの肝は、ズバリmain関数の再帰呼出関数ポインタにあります。 まず、main()のパラメータ「i」には、プログラム実行時は「1」が渡されます。(∵コマンドライン引数は渡さないでって、上に書いてありますでしょ??^^;) iが0より大きい場合、 tagC構造体のポインタに、malloc()でメモリが割り当てられ(72,74行目)、そして、関数ポインタに関数のアドレスが渡されます(73,75行目)。 初期値として「c -> c -> n2」 に「2」がセットされ、functionT()へと値が渡されます。 続いて functionT()へと目を向けてみます。 functionT()の第一パラメータと、戻り値のtagB構造体ポインタは、ここでは意味をなしていません。 そして、62,63行目に無限ループがあるからこそ、2進数の値が延々と表示されることになります。 62,63行目を、便宜上以下のコードに直して説明します。
A: for (;;) { B: b = (*paramC -> getTagB)(paramC -> n2, paramC -> c); C: main(-b -> n1); D: paramC = b -> c; E: }
初回呼出時、B行の (*paramC -> getTagB) は、functionW()を指しています。 functionW()の中を追いかけていくと、46行目にある(*paramC -> getTagB)はfunctionU()を指しており、 パラメータとして渡される paramC -> n2には、main()内で初期化された「2」が入っています。 結果として、C行でmain()を再帰呼出する際のパラメータには、「-2」が入ります。 (∵-b -> n1 ⇒ -(b -> n1)) (※ 19行目で「b -> c -> n2 = i + 1;」を行っていることから、次回は「-3」, ...となります。← これとっても重要!) 再帰呼出されたmain()では、79行目のputchar()内において、 i(← main()のパラメータ)が0以外の場合、「i/2」の値をパラメータとして、main()を再帰呼出します。 その後、iの絶対値が奇数ならば'1', 偶数ならば'0'を出力します。 一方、÷2, ÷2, ...を繰り返していくと、いずれmain()のパラメータが0となり、その場合はアスキーコード10(=改行)を出力し、 それ以上のmain()再帰呼出は行いません。 制御をD行に戻した後、paramC = b -> cを行っていることから、 以降のfunctionW()46行目では、functionU()の呼出ではなく、functionV()が呼び出される事になります。 ・・・と、ざっくりプログラムの流れを解説してみました^^;) IOCCCのグランプリ作品という事で、見た目のトリッキーさも要求される事から、無意味なコードも含まれています。 でも、「main関数の再帰呼出」と、「関数ポインタの使用」という点では、とても良いサンプルコードなのではないかなぁと思います♪
トリッキーコードネット の TOPへ HOTNEWS の 総合TOPへ