6f - Essays Room

Last modified on : Apr. 18, 2003.

不動点プログラム

不動点プログラムとは、自分自身のソースファイルを出力するプログラムのことです。ちょっとしたきっかけで、私はこれに、C言語で挑戦するところとなりました。細かいルールなどは知らないので、不明な点は自分でルールを作ってしまいました(苦笑)。C言語を知らない人にとっては、面白くない記事かもしれません。ご了承ください。

なお、ソース内の行頭にある→印は、前の行からの続きを表しています。

誤りなどのご指摘などは、掲示板(B)メイル(E)からお願いします。

戦略

不動点プログラムは、画面にソースを表示するプログラムです。ということで、入門書でよく見かける、こういったソースファイルをベースにしましょう。

#include <stdio.h>

int main ( void )
{
    printf( "はろはろ〜\n" );
    return 0;
}

(ツッコミを入れたくなった人へ:たまにはこんなソースを載せる入門書があってもいいじゃん!/笑)

このソースのはろはろ〜\nの部分に、このソースファイルそのものを書けば、不動点プログラムは完成です。

#include <stdio.h>

int main ( void )
{
    printf( "#include <stdio.h>\n(〜中略〜)printf( \"はろはろ〜\n\" );
→(〜中略〜)\n}\n" );
    return 0;
}

とまぁこんな感じになるのですが、今度は、さらにはろはろ〜\nの部分を、このソースファイルそのもので置き換えなければなりません。そうなると、次はまた、置き換えた内容の中のはろはろ〜\nの部分を以下略、となって、無限ループに陥ってしまいます。鏡合わせ遊びを彷彿とさせますね。

逆にいえば、これを解決すれば、不動点プログラムは完成するわけです。(これが不動点プログラムのキモです。)

ここで脱線。私の頭の中に、こんなソースファイルが浮かびました。

#include <process.h>

int main ( void )
{
    system( "type grape.cpp" );
    return 0;
}

いや、これはダメかな(汗)。ちなみにgrape.cppとはこのソースファイルの名前です。「不動点プログラム」の英訳がわからなかったので、「不動」「ふどう」「ぶどう」と変換(寒っ!)した結果だったりします。

というわけでルール追加。

「使用してよい関数はprintfのみとする(openfopenも不可)。」

話を元に戻しましょう。

printfの行は、ポインタを使って、こう書き直してもOKです。

char* str = "はろはろ〜\n";
printf( "%s", str );
char* str1 = "%s";
char* str2 = "はろはろ〜\n";
printf( str1, str2 );

ここで、str1str2とをまとめて、こう書き直してみると・・・。

char* str = "printf( %s, str );";
printf( str, str );

str内の%sに、str自身が展開され、出力はこうなります。もちろん、この展開は1回だけです。

printf( printf( %s, str );, str );

では、strをこう変えると・・・。

char* str = "char* str = \"%s\";\nprintf( str, str );";
printf( str, str );

こうなります。

char* str = "char* str = "%s"
printf( str, str );";
printf( str, str );

なんかもう、無茶苦茶ですが(汗)、実はかなり、いい線いっています。出力の、1行目に3つある"、2つ目と3つ目を\"に変えて、1行目の最後の改行を\nで置換(2行目と連結)すると、ソースそのものになります。

さてここで、"\nの文字コードは、10進数でそれぞれ34と10です。ですので、

printf( "10 = [%c], 34 = [%c]", 10, 34 );

これの出力は、こうなります。

10 = [
], 34 = ["]

これをうまく使えば、何とかなりそうですね。

char* str = "char* str = %c%s%c;%cprintf( str, 34, str, 34, 10 );";
printf( str, 34, str, 34, 10 );

そして出力は、こうなります。

char* str = "char* str = %c%s%c;%cprintf( str, 34, str, 34, 10 );";
printf( str, 34, str, 34, 10 );

うまくいったようです。

とりあえず完成

先ほどのソースをコアとして、プログラムを作成してみましょう。

#include <stdio.h>

char* str = "#include <stdio.h>%c%c
→char* str = %c%s%c;%c%c
→int main ( void )%c
→{%c
→    printf( str, 10, 10, 34, str, 34, 10, 10, 10, 10, 10, 10, 10 );%c
→    return 0;%c
→}%c";

int main ( void )
{
    printf( str, 10, 10, 34, str, 34, 10, 10, 10, 10, 10, 10, 10 );
    return 0;
}

出力は、こうなります。

#include <stdio.h>

char* str = "#include <stdio.h>%c%cchar* str = %c%s%c;%c%cint main ( void )%c{%c
    printf( str, 10, 10, 34, str, 34, 10, 10, 10, 10, 10, 10, 10 );%c    return 
0;%c}%c";

int main ( void )
{
    printf( str, 10, 10, 34, str, 34, 10, 10, 10, 10, 10, 10, 10 );
    return 0;
}

これで不動点プログラムは完成です。

減量大作戦

次は、これを減量してみましょう。

C言語の場合、連続した空白(スペース、タブ、改行)は、1つにまとめることができます。例えば、a [改行] = 0 ;a =   0 ;は、a = 0 ;と同じです。改行も、Cの文は;で終わるという規則があるため、省くことができます。ただし、プリプロセッサ命令(#で始まる行、ここでは#include文)は、命令の終わりは改行コードという規則のため、必ず1行に1文という制限があります。

この規則にしたがって、(連続した)空白をスペースひとつに置き換えると、こうなります。

#include <stdio.h>
char* str = "#include <stdio.h>%c
→char* str = %c%s%c; int main ( void ) { printf( str, 10, 34, str, 34 );
→ return 0; }";
→ int main ( void ) { printf( str, 10, 34, str, 34 ); return 0; }

ずいぶん読みにくくなってしまいました(汗)。2行のプログラムです。なお、最後の(}の後の)改行コードも、省略しました。

改行が減ったため、str内の%cprintfの引数が、かなり減っています。

では次に、さらに省けるスペースは省きましょう。省けるスペースとは、区切り文字の前後のスペースのことです。

具体的には、*=,;(){}の前後にあるスペースです。つまり、a = 0 ;a=0;と省略(?)できるわけです。また、#include<stdio.h>との間のスペースも省けます。

#include<stdio.h>
char*str="#include<stdio.h>%c
→char*str=%c%s%c;int main(void){printf(str,10,34,str,34);return 0;}";
→int main(void){printf(str,10,34,str,34);return 0;}

まだ省ける要素があります。関数の宣言の際、戻り値がint型の場合は省略が可能です。さらに、引数がない(void)の場合も、省略できます。

#include<stdio.h>
char*str="#include<stdio.h>%c
→char*str=%c%s%c;main(){printf(str,10,34,str,34);return 0;}";
→main(){printf(str,10,34,str,34);return 0;}

モノはついでですから、変数名も短くしてしまいましょう。

#include<stdio.h>
char*s="#include<stdio.h>%c
→char*s=%c%s%c;main(){printf(s,10,34,s,34);return 0;}";
→main(){printf(s,10,34,s,34);return 0;}

ようやく、不動点プログラムが完成しました。かなり複雑、というかは怪奇なモノが出来上がりました。我ながら、コンパイルして通ってしまうのが不思議です(笑)。

ダウンロード用にファイルを改めて用意するほどのものでもないので、試してみたい方は、コピー&ペーストして使ってください(笑)。138バイト(Unixでは137バイト)です。

余談

さらに減量できないか、というのを検討してみたのですが、思いつきませんでした。

#defineを用いるという方法が真っ先に思いつきましたが、文字列(char* sの右辺値)内では#defineによる置き換えがなされないため、断念。

他に面白そうなアプローチが見つかれば、試してみようと思います。

戻る(B)
トップ(T) | 掲示板(B) | メイル(E)
Valid XHTML 1.1! このページは正当なXHTML 1.1 文書であると評価されました。
W3CHTML検証サービスを用いこのページを検証(V)