不動点プログラムは、画面にソースを表示するプログラムです。ということで、入門書でよく見かける、こういったソースファイルをベースにしましょう。
#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のみとする(openやfopenも不可)。」
話を元に戻しましょう。
printfの行は、ポインタを使って、こう書き直してもOKです。
char* str = "はろはろ〜\n"; printf( "%s", str );
char* str1 = "%s"; char* str2 = "はろはろ〜\n"; printf( str1, str2 );
ここで、str1とstr2とをまとめて、こう書き直してみると・・・。
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内の%cとprintfの引数が、かなり減っています。
では次に、さらに省けるスペースは省きましょう。省けるスペースとは、区切り文字の前後のスペースのことです。
具体的には、*、=、,、;、(、)、{、}の前後にあるスペースです。つまり、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による置き換えがなされないため、断念。
他に面白そうなアプローチが見つかれば、試してみようと思います。