はじめに
C言語でQuineを書いてみました。
Quineとは、簡単にいうと「自分自身のソースコードを出力するプログラム」です。
つまり、例えば main.c を実行すると、出力結果が main.c の中身と完全に一致するようなプログラムです。
最初に聞くと「それって cat main.c すればいいのでは?」と思うかもしれませんが、Quineでは基本的に外部ファイルを読み込まずに、自分自身の内容を出力する必要があります。
Quineの条件
Quineには、だいたい以下のような条件があります。
- 自分自身のソースコードを出力する
- ソースファイルを直接読み込まない
- 出力結果が元のソースコードと完全に一致する
今回はC言語で、なるべくシンプルなQuineを書いてみます。
完成コード
#include <stdio.h>
int main(void)
{
char *s = "#include <stdio.h>%c%cint main(void)%c{%c char *s = %c%s%c;%c printf(s, 10, 10, 10, 10, 34, s, 34, 10, 10, 10);%c return 0;%c}%c";
printf(s, 10, 10, 10, 10, 34, s, 34, 10, 10, 10);
return 0;
}
実行してみる
例えば、上のコードを main.c として保存します。
gcc main.c -o quine
./quine
実行すると、自分自身のソースコードが出力されます。
さらに確認するなら、出力結果を別ファイルに保存して diff してみます。
./quine > output.c
diff main.c output.c
何も表示されなければ、main.c と output.c は一致しています。
仕組み
ポイントはこの部分です。
char *s = "...";
printf(s, ...);
Quineでは、自分自身のコードを文字列として持ち、その文字列を printf で出力します。
ただし、単純にソースコード全体を文字列に入れようとすると問題があります。
例えば、C言語で文字列を表すには " が必要です。
char *s = "hello";
しかし、ソースコード自身を文字列として扱う場合、その中にある " も出力しなければいけません。
そこで、" を直接文字列に書く代わりに、ASCIIコードを使っています。
34
ASCIIコードの 34 は " を表します。
また、改行には 10 を使っています。
10
ASCIIコードの 10 は改行を表します。
つまり、この部分では、
printf(s, 10, 10, 10, 10, 34, s, 34, 10, 10, 10);
%c に改行やダブルクォートを埋め込み、%s に文字列 s 自身を埋め込むことで、自分自身のコードを再構築しています。
フォーマット文字列を見る
文字列 s の中には、出力したいソースコードの雛形が入っています。
char *s = "#include <stdio.h>%c%cint main(void)%c{%c char *s = %c%s%c;%c printf(s, 10, 10, 10, 10, 34, s, 34, 10, 10, 10);%c return 0;%c}%c";
この中で重要なのは以下です。
-
%c: 文字を1文字出力する -
%s: 文字列を出力する
%c には改行や " を渡します。
%s には、文字列 s 自身を渡します。
printf(s, ..., s, ...);
ここがQuineっぽいところです。
自分自身を表す文字列 s を、出力の途中にもう一度埋め込んでいます。
なぜ s を2回使うのか
Quineでは、プログラム本体を出力するだけでなく、プログラム中にある「プログラム本体を表す文字列」も出力する必要があります。
つまり、以下の2つを同時に満たす必要があります。
- ソースコード全体を出力する
- そのソースコードの中にある文字列
sの中身も正しく出力する
このため、文字列 s は、
- 出力のテンプレートとして使われる
- ソースコード内の文字列リテラルとしても出力される
という2つの役割を持っています。
ハマりやすいポイント
ダブルクォートの扱い
一番ややこしいのは " の扱いです。
C言語では文字列を " で囲むため、ソースコードとして " を出力したい場合は工夫が必要です。
今回は 34 を使って出力しています。
printf("%c", 34);
これは以下と同じ意味です。
printf("\"");
改行の扱い
改行も直接文字列に書くと見通しが悪くなるため、今回は 10 を使っています。
printf("%c", 10);
これは \n と同じように改行を出力します。
出力結果の完全一致
Quineでは、空白や改行の数も含めて一致している必要があります。
そのため、インデントや末尾の改行がずれていると、diff で差分が出ます。
見た目では同じに見えても、実際には改行やスペースが違うことがあります。
まとめ
C言語でQuineを書いてみました。
特に難しいのは、自分自身を文字列として持ちながら、その文字列自身も正しく出力するところです。
今回のポイントは以下です。
- ソースコードの雛形を文字列
sとして持つ -
printfでsを出力する -
%sにs自身を渡す -
%cを使って改行や"を出力する - 出力結果がソースコードと完全一致するように調整する
最初は少し不思議な感じがしますが、仕組みがわかると「なるほど」となる面白いプログラムでした。