我读了肯·汤普森的经典论文Reflections on Trusting Trust,其中他提示用户写一个Quine作为他的论点的介绍(强烈推荐阅读)。
quine是一个计算机程序,它不接受任何输入并生成自己的源代码副本作为其唯一的输出。
天真的做法只是想说:
print "[insert this program's source here]"
但很快就会发现这是不可能的。我最终使用Python writing one myself,但仍然无法解释“诀窍”。 我正在寻找一个很好的解释为什么奎因是可能的。
答案 0 :(得分:13)
通常的技巧是使用printf
,使得format-string表示程序的结构,字符串本身的占位符可以获得所需的递归:
来自http://www.nyx.net/~gthompso/quine.htm的标准C示例非常清楚地说明了这一点:
char*f="char*f=%c%s%c;main(){printf(f,34,f,34,10);}%c";main(){printf(f,34,f,34,10);}
编辑:写完这篇文章后,我做了一些搜索:http://www.madore.org/~david/computers/quine.html给出了一个非常好的,更理论化的描述,说明了究竟是什么以及它们的工作原理。
答案 1 :(得分:3)
我写的是使用putchar
代替printf
的内容;因此它必须处理所有自己的转义码。但它在所有C执行字符集中都是可移植的%100。
你应该能够看到文本表示中有一个 seam ,它反映了程序文本本身的 seam ,它从一开始就改变了到最后工作。 写一个Quine的诀窍就是克服这个“驼峰”,在那里你转向挖掘 out 这个洞的方式!你的选择受到文本表示的限制和语言的输出设施。
#include <stdio.h>
void with(char *s) {
for (; *s; s++) switch (*s) {
case '\n': putchar('\\'); putchar('n'); break;
case '\\': putchar('\\'); putchar('\\'); break;
case '\"': putchar('\\'); putchar('\"'); break;
default: putchar(*s);
}
}
void out(char *s) { for (; *s; s++) putchar(*s); }
int main() {
char *a[] = {
"#include <stdio.h>\n\n",
"void with(char *s) {\n",
" for (; *s; s++) switch (*s) {\n",
" case '\\",
"n': putchar('\\\\'); putchar('n'); break;\n",
" case '\\\\': putchar('\\\\'); putchar('\\\\'); break;\n",
" case '\\\"': putchar('\\\\'); putchar('\\\"'); break;\n",
" default: putchar(*s);\n",
" }\n}\n",
"void out(char *s) { for (; *s; s++) putchar(*s); }\n",
"int main() {\n",
" char *a[] = {\n",
NULL }, *b[] = {
"NULL }, **p;\n",
" for (p = a; *p; p++) out(*p);\n",
" for (p = a; *p; p++) {\n",
" putchar('\\\"');\n",
" with(*p);\n",
" putchar('\\\"'); putchar(','); putchar('\\",
"n');\n",
" }\n",
" out(\"NULL }, *b[] = {\\",
"n\");\n",
" for (p = b; *p; p++) {\n",
" putchar('\\\"');\n",
" with(*p);\n",
" putchar('\\\"'); putchar(','); putchar('\\",
"n');\n",
" }\n",
" for (p = b; *p; p++) out(*p);\n",
" return 0;\n",
"}\n",
NULL }, **p;
for (p = a; *p; p++) out(*p);
for (p = a; *p; p++) {
putchar('\"');
with(*p);
putchar('\"'); putchar(','); putchar('\n');
}
out("NULL }, *b[] = {\n");
for (p = b; *p; p++) {
putchar('\"');
with(*p);
putchar('\"'); putchar(','); putchar('\n');
}
for (p = b; *p; p++) out(*p);
return 0;
}
一个常见的技巧是通过编写程序读取文本文件并输出数字数组来跳转启动。然后修改它以使用静态数组,并针对新的(静态数组)程序运行第一个程序,生成一个代表程序的数字数组。将其插入静态数组,再次运行直到它稳定下来,这样就可以得到一个quine。 但是,它与特定字符集绑定(==不是100%可移植)。像上面这样的程序(而不是经典的 printf hack)在ASCII或EBCDIC上的工作方式相同(经典的 printf 黑客在EBCDIC中失败,因为它包含硬编码的ASCII )。
编辑:
再次仔细阅读问题(最后),看来你实际上正在寻找更多的哲学技术。让你摆脱无限回归的诀窍是 two-fer 。您必须从相同的数据中获取编码程序和扩展程序:使用相同的数据2种方式。因此,该数据仅描述了围绕其未来表现的程序部分,框架。此框架内的图像是原始图像的直接副本。
这就是你如何自然地手工制作递归绘图:电视电视的电视。在某些时候你会感到疲倦,只是在屏幕上画出一些眩光,因为递归已经足够了。
编辑:
我正在寻找Quines可行的绝佳解释。
奎因的“可能性”进入了19世纪和20世纪数学革命的深度。 W. V. O. Quine的“经典”quine是词序列(IIRC)
在附加到自身时产生错误
这是一个悖论,类似于大卫要求的东西“让我快乐,悲伤时让我伤心,让我快乐时感到伤心”,双方刻上的奖章回答:“这也应该过去了。”
同样的结被现代数学逻辑的先驱者调查,如弗雷格,罗素和怀特黑德,Łukasiewicz,当然还有我们的男孩图灵,教会和Thue。这种技巧可以将Quine从文字游戏领域转换到编程演示(沿途解开 paradox 部分),这是哥德尔编码算法的方法操作本身作为数字,因此整个数学表达式可以编码为单个(巨大的)整数。特别地,执行该表示的解码的数学函数可以以相同(数字)形式表示。这个数字(哥德尔编码函数) 代码和数据。
此功率三重奏(代码,表示,数据)可以转换为不同的重复。通过选择不同的表示(或链,如:bytes-&gt; ASCII-&gt;十六进制 - >整数),可以改变代码的行为,从而改变数据的外观。