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

【芸術的な凄いプログラミング】IOCCC作品のコード解説7:バイナリエディタ プログラミング

1986年(第三回)IOCCCグランプリ作品中に、なんとバイナリエディタ表示コードが投稿されていました!
以下にご紹介します~^^;)




#include <stdio.h> #define O1O printf #define OlO putchar #define O10 exit #define Ol0 strlen #define QLQ fopen #define OlQ fgetc #define O1Q abs #define QO0 for typedef char lOL; lOL*QI[] = {"Use:\012\011dump file\012","Unable to open file '\x25s'\012", "\012"," ",""}; main(I,Il) lOL*Il[]; { FILE *L; unsigned lO; int Q,OL[' '^'0'],llO = EOF, O=1,l=0,lll=O+O+O+l,OQ=056; lOL*llL="%2x "; (I != 1<<1&&(O1O(QI[0]),O10(1011-1010))), ((L = QLQ(Il[O],"r"))==0&&(O1O(QI[O],Il[O]),O10(O))); lO = I-(O<<l<<O); while (L-l,1) { QO0(Q = 0L;((Q &~(0x10-O))== l); OL[Q++] = OlQ(L)); if (OL[0]==llO) break; O1O("\0454x: ",lO); if (I == (1<<1)) { QO0(Q=Ol0(QI[O<<O<<1]);Q=' '&&l<='\~'; }
使い方は、パラメータにファイルパスを渡すと、バイナリコードとアスキーコードを表示してくれます。 IOCCC作品の解説:バイナリエディタ それでは早速解説を始めます^^;) まずは、いつもの如くプリプロセッサの実行結果を表示させ、ついでに typedef されている 「IOL」を「char」に戻します。
#include <stdio.h> char*QI[] = {"Use:\012\011dump file\012","Unable to open file '\x25s'\012", "\012"," ",""}; main(I,Il) char*Il[]; { FILE *L; unsigned lO; int Q,OL[' '^'0'],llO = (-1), O=1,l=0,lll=O+O+O+l,OQ=056; char*llL="%2x "; (I != 1<<1&&(printf(QI[0]),exit(1011-1010))), ((L = fopen(Il[O],"r"))==0&&(printf(QI[O],Il[O]),exit(O))); lO = I-(O<<l<<O); while (L-l,1) { for(Q = 0L;((Q &~(0x10-O))== l); OL[Q++] = fgetc(L)); if (OL[0]==llO) break; printf("\0454x: ",lO); if (I == (1<<1)) { for(Q=strlen(QI[O<<O<<1]);Q=' '&&l<='\~'; }
変数名がトリッキー過ぎる為、以下の様な 多少分かりやすい(?)変数名に置換します。
元変数名新変数名
QIstr
Ilargv
Iargc
llOk
llls
OLj
OQt
llLpsz
lOio
Qi
Oq
lr
Lfp
ついでに、インデントが意図的にずれている為、適当に修正します。
1 : #include <stdio.h> 2 : 3 : char *str[] = { "Use:\012\011dump file\012", "Unable to open file '\x25s'\012", "\012", " ", "" }; 4 : 5 : main(argc,argv) 6 : char *argv[]; 7 : { 8 : FILE *fp; 9 : unsigned io; 10 : int i, j[' ' ^ '0'], k = (-1), q = 1, r = 0, s = q+q+q+r, t = 056; 11 : char *psz = "%2x "; 12 : 13 : (argc != 1<<1 && (printf(str[0]), exit(1011-1010))), 14 : ( (fp = fopen(argv[q], "r")) == 0 && (printf(str[q], argv[q]), exit(q)) ); 15 : 16 : io = argc-(q<<r<<q); 17 : 18 : while (fp-r,1) 19 : { 20 : for(i = 0L; ((i &~(0x10-q))== r); j[i++] = fgetc(fp)); 21 : 22 : if (j[0]==k) 23 : break; 24 : 25 : printf("\0454x: ",io); 26 : 27 : if (argc == (1<<1)) 28 : { 29 : for (i = strlen(str[q<<q<<1]); i= ' ' && r <= '\~'; 49 : }
アスキーコード表と見比べるに、3行目の str[0]は "Use:\n\tdump file\n" と、 str[1]は "Unable to open file '%s'\n" と、 str[2]は "\n" と、 それぞれ同義です。 また、10行目の j[' ' ^ '0'] は j[16] と (∵ 32 ^ 48 = 16)、 s = q+q+q+r は s = 3 と、 t = 056 は t = 46 と (∵ 056は八進数)、 それぞれ同義です。 20行目の (i &~(0x10-q)) は (i &~ 15) と同義です。(∵ 0x10 = 16, q = 0) そして、iが0~15の場合、(i &~ 15) は ずっと0です。 しかし、iが16になった場合、初めて(i &~ 15) は 0以外となります。 また、r = 0の為、 20行目は 「ファイルポインタfpから16文字を読み込み、配列jに格納する」という意味になります。 25行目の printf関数の第一パラメータは、"%4x: " と同義です。 37行目の 46は、アスキーコード表と照らし合わせて、.(ドット)だという事が分かります。 41行目の 01^10^9 は 2と同義です。 上記の解説を反映させ、 かつ、随所にあるシフト演算、足し算・引き算、値が固定である 変数k, q, r, s, t を、分かりやすく整数で置換します。 すると、以下の様なコードになります。
1 : #include <stdio.h> 2 : 3 : char *str[] = { "Use:\n\tdump file\n", "Unable to open file '%s'\n", "\n", " ", "" }; 4 : 5 : main(argc,argv) 6 : char *argv[]; 7 : { 8 : FILE *fp; 9 : unsigned io; 10 : int i, j[16]; 11 : char *psz = "%2x "; 12 : 13 : (argc != 2 && (printf(str[0]), exit(1))), 14 : ( (fp = fopen(argv[1], "r")) == 0 && (printf(str[1], argv[1]), exit(1)) ); 15 : 16 : io = argc - 2; 17 : 18 : while (fp,1) 19 : { 20 : for(i = 0; i != 16; j[i++] = fgetc(fp)); 21 : 22 : if (j[0] == -1) 23 : break; 24 : 25 : printf("%4x: ",io); 26 : 27 : if (argc == 2) 28 : { 29 : for (i = strlen(str[4]); i= ' ' && r <= '\~'; 49 : }
3行目の str[4]は、使わなくなったので削除します。 str[3]は、配列から削除し、プログラム中の使用箇所は 「" "」 で置換します。 str[2]は、配列から削除し、プログラム中の使用箇所は 「"\n"」 で置換します。 16行目の argc - 2 は、argcが2以外の場合は13行目でプログラムが終了してしまう為、0です。 29行目の strlen(str[4])は、strlen("")なので0、 strlen(str[0])は、16です。(← 偶然ではなく、ワザと余計なスペースやタブを入れて、str[0]の文字列長が16になるようにしてあります) 35行目の i += i<64 は、i++と同義です。(∵ i<64 は trueと評価され、演算中では1として扱われます) 38行目のputchar(32)は、(アスキーコード表と照らし合わせて) putchar(' ')と同義です。 これらを反映させたコードは、下記の通りです。
1 : #include <stdio.h> 2 : 3 : char *str[] = { "Use:\n\tdump file\n", "Unable to open file '%s'\n" }; 4 : 5 : main(argc,argv) 6 : char *argv[]; 7 : { 8 : FILE *fp; 9 : unsigned io; 10 : int i, j[16]; 11 : char *psz = "%2x "; 12 : 13 : (argc != 2 && (printf(str[0]), exit(1))), 14 : ( (fp = fopen(argv[1], "r")) == 0 && (printf(str[1], argv[1]), exit(1)) ); 15 : 16 : io = 0; 17 : 18 : while (fp,1) 19 : { 20 : for(i = 0; i != 16; j[i++] = fgetc(fp)); 21 : 22 : if (j[0] == -1) 23 : break; 24 : 25 : printf("%4x: ",io); 26 : 27 : if (argc == 2) 28 : { 29 : for (i = 0; i < 16; i++) 30 : printf( (j[i] != -1) ? psz : " ", j[i]); 31 : 32 : printf(" "); 33 : } 34 : 35 : for (i = 0; i < 16; i++) 36 : { 37 : (j[i] != -1) ? ((D(j[i]) == 0 && (*(j + i) = '.')), putchar(j[i])) 38 : : putchar(' '); 39 : } 40 : 41 : printf("\n"); 42 : io += i; 43 : } 44 : } 45 : 46 : D(r) 47 : { 48 : return r >= ' ' && r <= '\~'; 49 : }
これで、どこにでもある普通のC言語ソースコードになりましたYO~~^^;) 一応プログラムの流れを説明します。 パラメータが指定されていないと、 13行目で「ダンプするファイルが必要だよ」という旨のメッセージを表示して、プログラム終了。 その後、ダンプするファイルの読み込みに失敗した場合、 「ファイルを開くのに失敗しました」という旨のメッセージを表示して、プログラム終了。 (↑この際、ココで紹介した技を使っています) 20行目でファイルの内容を1byteずつ読み込みます。 fgetc関数を使用している為、ファイルの読み込みに失敗した場合は -1が返されます。 (↑これ、このソースコードを読む際に、非常に重要になってきます) 30行目では、 ファイルの読み込みに成功している場合は、"%2x"でバイナリの値をダンプ、 ファイルの読み込みに失敗している場合は、スペースを表示します。 37,38行目では、 ファイルの読み込みに成功している場合、 該当1byteを関数Dに渡し、戻りが0の場合はドットを表示します。戻りが1の場合は、アスキー文字の範囲の為そのまま表示します。 ファイルの読み込みが失敗している場合、スペースを表示します。 46~48行目の関数Dは、入力文字が スペース ~ ~(チルダ)の範囲にあるかを判定して結果を返します。 これにより、入力文字がアスキー文字か否かを判定します。 ・・・といったわけで、ファイルを読み込み、バイナリコードとアスキー文字を表示するプログラムの流れが分かりました♪ いやぁ~~、IOCCCのコードリーディングって面白いですね~~^^
トリッキーコードネット の TOPへ HOTNEWS の 総合TOPへ