不動点プログラムは、画面にソースを表示するプログラムです。ということで、入門書でよく見かける、こういったソースファイルをベースにしましょう。
#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
による置き換えがなされないため、断念。
他に面白そうなアプローチが見つかれば、試してみようと思います。