引用元:https://www.ioccc.org/1984/mullender/mullender.c
審査員・作者による説明:https://www.ioccc.org/1984/mullender/hint.html
動作
顔文字 :-)
が右にスクロールしていくアニメーション。
$ ./mullender
:-)
$ ./mullender
:-)
$ ./mullender
:-)
解説
第1回IOCCC優勝作品。IOCCCの中でも、有名な作品の1つ。
main
が関数でなく整数列として定義されている。この整数列は機械語を数字で書いたもの。C言語プログラムは、main
というシンボルが指す先にcallで飛び込んで実行し始めるので、こういう技が原理的には可能。
なおこれは、PDP-11という1980年頃のコンピュータの機械語なので、現代のマシンで直接動かすことはできない。PDP-11エミュレータを用意する必要があるだろう。[[2015/endoh3]]や[[2018/mills]]を使うと動作確認できる。
おそらくこの作品がきっかけで、1985年の第2回IOCCCからは、「ポータブルなコードに加点をする」というルールが追加された。
このmain
の定義をPDP-11の機械語として解釈した場合の意味を簡単に説明しておく。やっていることは大まかに次の通り。
- 文字列データへのアドレスを計算する
write
システムコールを呼んで" :-)\b\b\b\b"
を表示する(\b
はバックスペース文字)
- ウェイトを置く
- 1へ戻る
これを踏まえて、要点だけ逆アセンブルしたものを読んで欲しい。なお、数字は基本的に8進数で考える。
main+064
の命令によってmain+076
にある即値0は文字列のアドレスに書き換えられることに注意。
# エントリポイント
main+000: 000425 BR 25 # PC+2 + 25*2(= main+054)へジャンプする(数字が8進数であることに注意)。
# 表示する文字列データ
main+043 .. main+053: " :-)\b\b\b\b"
# 文字列データへのアドレスをR4に入れる
main+054: 010704 MOV PC, R4 # PC+2(= main+056)をR4に代入する。
main+056: 005744 TST -(R4) # R4から2を引き、テストをする(が、テスト結果は使っていない)。R4はmain+054になる。
main+060: 162704 000011 SUB #11, R4 # R4から11を引く。R4はmain+043になる。
# システムコールのwrite(1,main+043,9)を呼ぶ
main+064: 010467 MOV R4, 6 # PC+4 + 6(つまりmain+076)のメモリにR4の値を格納する。
main+070: 012700 000001 MOV #1, R0 # R0に1を代入する
main+074: 104404 TRAP 4 # writeシステムコールを呼ぶ(第1引数はR0 = 1、つまり標準出力に書く)。
main+076: 000000 即値0 # writeに渡す第2引数。文字列のアドレス = main+043。
main+100: 000011 即値11 # writeに渡す第3引数。文字列の長さ = 10進数で言うと9。
# ウェイトを置く
main+102: 012702 001000 MOV #1000, R2 # R2に1000を代入する
main+106: 104455 TRAP 55 # ダミーのシステムコール(ウェイトとして使っている)。
main+110: 077202 SOB R2, 2 # R2から1を引き、その結果が0でなければPC-2*2(つまりmain+106)にジャンプする。
# ループする
main+112: 000760 BR -20 # PC+2 - 20*2の番地(= main+054)へジャンプする。
なお、PDP-11で使っていない空間にはVAX-11用の命令列が入っているらしい。
調べようと思ったら、すでに調べている人がいたのでリンクのみ掲載する。