トリッキーコードネット トップへ戻る   C/C++, Java, Perl, PHP, JavaScript, アルゴリズム, ショートコーディング, IOCCCコードの解説, 等々

サイト情報

トリッキーなコード

7行プログラミング

物凄いコード集

アルゴリズム

データ構造

C/C++な話題

コードサンプル

ツール/環境構築

開発ノウハウ 等

ネタ/ジョーク集

おススメ書籍/サイト

サイトTOP >> 物凄いコード集 >> 入力文字列が走り去るっ! (C言語)

入力した文字列が走り去るっ! (C言語 - IOCCC)

1985年(第二回)IOCCCの入賞作品をご紹介します。

下記のコードをパラメータ無しで実行し、長~い文字列を入力します。
#define o define
#o ___o write
#o ooo (unsigned)
#o o_o_ 1
#o _o_ char
#o _oo goto
#o _oo_ read
#o o_o for
#o o_ main
#o o__ if
#o oo_ 0
#o _o(_,__,___)(void)___o(_,__,ooo(___))
#o __o (o_o_<<((o_o_<<(o_o_<<o_o_))+(o_o_<<o_o_)))+(o_o_<<(o_o_<<(o_o_<<o_o_)))
o_(){_o_ _=oo_,__,___,____[__o];_oo ______;_____:___=__o-o_o_; _______:
_o(o_o_,____,__=(_-o_o_<___?_-o_o_:___));o_o(;__;_o(o_o_,"\b",o_o_),__--);
_o(o_o_," ",o_o_);o__(--___)_oo _______;_o(o_o_,"\n",o_o_);______:o__(_=_oo_(
oo_,____,__o))_oo _____;}
すると、あら不思議・・・、入力文字列がひゅ~~んと流れるように走り、何処へと消えていきます ^^;) 入力文字列が走り去る (C言語 - IOCCC) (↑上の aaa... が入力文字列、その下の aaaaa が、走りながらだんだんと短くなって消えて行く最中の文字列です。) 相変わらずIOCCCのコードは、パッと見 意味不明なコードですが、とりあえず正常に動作する事を確認してしまったので(笑)、 解説を始めます。 まず、冒頭のコードをVC++2010で実行すると、次の様なエラーが出てコンパイラに怒られます。 ---------------------------------------------------------------- fatal error C1021: プリプロセッサ コマンド 'o' が無効です。 ---------------------------------------------------------------- #oより先に「#define o define」が定義してあるので、 プリプロセッサが#oを#defineと解釈してくれてもよさそうなのですが、 どうやらVC++2010ではそうもいかないみたいです。 そこで、冒頭のコードを以下の様に書き換えます。
#define ___o write
#define ooo (unsigned)
#define o_o_ 1
#define _o_ char
#define _oo goto
#define _oo_ read
#define o_o for
#define o_ main
#define o__ if
#define oo_ 0
#define _o(_,__,___)(void)___o(_,__,ooo(___))
#define __o (o_o_<<((o_o_<<(o_o_<<o_o_))+(o_o_<<o_o_)))+(o_o_<<(o_o_<<(o_o_<<o_o_)))
o_(){_o_ _=oo_,__,___,____[__o];_oo ______;_____:___=__o-o_o_; _______:
_o(o_o_,____,__=(_-o_o_<___?_-o_o_:___));o_o(;__;_o(o_o_,"\b",o_o_),__--);
_o(o_o_," ",o_o_);o__(--___)_oo _______;_o(o_o_,"\n",o_o_);______:o__(_=_oo_(
oo_,____,__o))_oo _____;}
これで、VC++2010でもコンパイルが通るようになりました^^;) つづいて、ここで紹介した技を使い、VC++にプリプロセッサの実行結果を表示させます。
main(){char _=0,__,___,____[(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))];goto ______;_____:___=(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))-1; _______:
(void)write(1,____,(unsigned)(__=(_-1<___?_-1:___)));for(;__;(void)write(1,"\b",(unsigned)(1)),__--);
(void)write(1," ",(unsigned)(1));if(--___)goto _______;(void)write(1,"\n",(unsigned)(1));______:if(_=read(
 0,____,(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))))goto _____;}
そして、このままでは非常に読み辛い為、適当なインデントを入れます。
main()
{
    char _ = 0,__,___,____[(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))];
    goto ______;


_____:
    ___ = (1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))-1;


_______:
    (void)write(1,____,(unsigned)(__=(_-1<___?_-1:___)));

    for (;__;(void)write(1,"\b",(unsigned)(1)),__--);

    (void)write(1," ",(unsigned)(1));

    if(--___)
        goto _______;

    (void)write(1,"\n",(unsigned)(1));


______:

    if(_=read(0,____,(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))))
        goto _____;
}
これで、なんとな~くプログラムの構造が見えてきたんじゃないかな??という気がします^^;) しかし、まだまだコードが読み辛い為、変数名・ラベル名を置換します。
 1 : main()
 2 : {
 3 :     char c1 = 0, c2, c3, c4[(1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))];
 4 :     goto LABEL6;
 5 :
 6 :
 7 : LABEL5:
 8 :     c3 = (1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))-1;
 9 :
10 :
11 : LABEL7:
12 :     (void)write(1, c4, (unsigned)(c2 = (c1-1 < c3 ? c1-1: c3)));
13 :
14 :     for (; c2; (void)write(1, "\b", (unsigned)(1)), c2--);
15 :
16 :     (void)write(1, " ", (unsigned)(1));
17 :
18 :     if (--c3)
19 :         goto LABEL7;
20 :
21 :     (void)write(1, "\n", (unsigned)(1));
22 :
23 :
24 : LABEL6:
25 :
26 :     if (c1 = read(0, c4, (1<<((1<<(1<<1))+(1<<1)))+(1<<(1<<(1<<1)))))
27 :         goto LABEL5;
28 : }
これで下準備は終わりました。 後は、先頭から順にソースコードを読んでいくだけです。 3行目で宣言されているc4は、char型が80個の配列です。 ∵ (1<<((1<<(1<<1))+(1<<1))) = 64, (1<<(1<<(1<<1))) = 16 で、 64 + 16 = 80! 同様に、8行目の c3の中には79が代入され、26行目のread関数の第三パラメータには80が渡されます。 これらを踏まえると、コードは以下の様に修正できます。
 1 : main()
 2 : {
 3 :     char c1 = 0, c2, c3, c4[80];
 4 :     goto LABEL6;
 5 :
 6 :
 7 : LABEL5:
 8 :     c3 = 79;
 9 :
10 :
11 : LABEL7:
12 :     (void)write(1, c4, (unsigned)(c2 = (c1-1 < c3 ? c1-1: c3)));
13 :
14 :     for (; c2; (void)write(1, "\b", (unsigned)(1)), c2--);
15 :
16 :     (void)write(1, " ", (unsigned)(1));
17 :
18 :     if (--c3)
19 :         goto LABEL7;
20 :
21 :     (void)write(1, "\n", (unsigned)(1));
22 :
23 :
24 : LABEL6:
25 :
26 :     if (c1 = read(0, c4, 80))
27 :         goto LABEL5;
28 : }
プログラムが開始され、変数を宣言した後、4行目のgotoで24行目にジャンプします。 26行目では、標準入力から 配列c4へ、80byte分データを読み込みます。 そして、読み込んだデータサイズを 変数c1へ代入します。 (※ ただEnterキーを押しただけでも、配列c4の中には改行が入り、変数c1には1が代入されます) 27行目のgotoで、7行目へとジャンプします。 8行目では、変数c3に 配列c4の最後の添字を格納します。 12行目では、標準出力に 配列c4の値を書き込み(表示)します。 書き込むサイズは、入力文字数長 と 配列の最大添字の、どちらか小さい方です。 その「書き込みサイズ」を、変数c2に代入します。 14行目では、その変数c2の値だけ、ループでバックスペースキーを表示します。 そして、16行目でスペースを表示し、18行目で変数c3の値をデクリメントします。 変数c3の値が 0以上であれば、19行目のgotoで再び11行目へと戻ります。 つまり、 12~19行目の繰り返しにより、 配列c4の表示位置と長さがじょじょにずれて行く事で、 入力文字列が走り去っていくように見える というわけでした~~^^;) そして、文字列が走り去った後は、 21行目で改行を表示し、再び26行目で文字列入力を受け付ける、(以下繰り返し) めでたし、めでたし・・・チャンチャン♪ ※このコードのロジックがいまいち見えてこない人は、  16行目でスペースの変わりに別の文字を表示させたり、17行目にsleep関数を入れると、  挙動が分かりやすくなります。
         このエントリーをはてなブックマークに追加   


作業効率化・ライフハックのオススメ記事




コンピュータ・テクノロジーのオススメ記事





恋愛・人間関係のオススメ記事




※ 当サイトは、トップページからリンクで辿る事の出来るページに限り、リンクフリーです。
※ 当サイトの閲覧/利用によって生じた如何なる損害も、当サイト管理人は責任を負いません。
※ 当サイトの内容を転載される場合は、当サイトへのリンクをお願い致します。