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<='\~';
}
使い方は、パラメータにファイルパスを渡すと、バイナリコードとアスキーコードを表示してくれます。
それでは早速解説を始めます^^;)
まずは、いつもの如くプリプロセッサの実行結果を表示させ、ついでに 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<='\~';
}
変数名がトリッキー過ぎる為、以下の様な 多少分かりやすい(?)変数名に置換します。
元変数名 | | 新変数名 |
QI | ⇒ | str |
Il | ⇒ | argv |
I | ⇒ | argc |
llO | ⇒ | k |
lll | ⇒ | s |
OL | ⇒ | j |
OQ | ⇒ | t |
llL | ⇒ | psz |
lO | ⇒ | io |
Q | ⇒ | i |
O | ⇒ | q |
l | ⇒ | r |
L | ⇒ | fp |
ついでに、インデントが意図的にずれている為、適当に修正します。
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のコードリーディングって面白いですね~~^^