Last active
December 16, 2015 17:50
-
-
Save ichaos/5473364 to your computer and use it in GitHub Desktop.
self print program
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
可以编写一个程序打印出自身的代码么?怎么写呢?为什么呢? | |
首先,当然是可以的,我们可以打开保存源代码的文件,把文件内容打印出来,当然就是代码本身了。但是,这里实际上程序和代码是分离的,不能算严格意义上的自我表示,二进制代码在cpu上执行,它读取出磁盘的文件并打印出来,依赖于代码文件,没有实现自包含,如果代码文件不在就无法工作了。 | |
下面一个C版本完全自包含的实现: | |
char b = 0x22;char n = 0xa;char *c="char b = 0x22;char n = 0xa;char *c=%c%s%c;main(){printf(a, b, a, b);}";main(){printf(c, b, c, b);} | |
不考虑代码存在文件里面结尾的那个换行符,它打印出了它自己。 | |
这个是怎么写出来的呢? | |
首先,在C语言里,打印一般是用printf函数,打印字符串需要“%s”然后加参数,具体形式如下: | |
char *s = "I am a string"; | |
printf("%s", s); | |
这里,我们可以把问题简化一下,如果希望用这两句话打印出这两句话本身,我们该怎么办?很直观的,我们要用字符串s表示这两句代码,然后把它打印出来,像这样: | |
char *s = "X"; | |
printf("%s", s); | |
并且X = " | |
char *s = "X"; | |
printf("%s", s); | |
" | |
但是,好像有什么不对劲,如果我们试着把X展开,会发现X产生了一个新的X,新的X又指向一个新的X,永远没有办法找到最终的答案。为什么? | |
直接的原因在于,X试图表示一个包含自己并超过自己的集合就像 X = {1,X}。(内在深层次的原因还没想清楚,不知道有没有人给个解释)。 | |
那么接下来怎么办呢?X不能等于比自身多或者比自身少的东西呢。 | |
我们可以蒙一下(别问为什么,其实我不知道这一步到底是怎么出来的,它就出来了。。。),像这样, | |
char *s = "%s"; | |
printf(s, s); | |
这两句话打印出了%s,并且代码中%s和打印出来的%s是相同数目,这很关键。为什么?它解决了前一种思路遇到到的根本性问题。对于打印语句来说,必须要有的就是"%s",因为没有这个字符C代码不会帮你打印(其它奇怪的打印方式我们先不讨论),但是如果代码中有了"%s",而我们需要打印出代码中的"%s",就意味着字符串s中要有"%s",这样代码中就有了两个"%s",那字符串s中就要有2个"%s"...于是我们就回到了X的问题,即要打印出的"%s"字符少于整个代码中存在的"%s"字符数。但是,通过上面的这种形式,我们规避了这个问题,程序中只有一个%s,并且只打印出一个%s。那么下面的问题似乎就变得清晰了,如果"char *s = "在这里也是必不可少的话,我们很自然的想到把它整个塞进s字符串中会怎么样呢? | |
char *s = "char *s = %s"; | |
printf(s, s); | |
这两句代码打印出来就是: | |
char *s = char *s = %s; | |
是的,很神奇的是,和源代码几乎一样,除了少了两个冒号,冒号的问题我们一会儿再讨论,这里神奇的背后其实是这样的原理,假设, | |
char *a = "b%s"; | |
printf(a, a); | |
代码打印的时候实际上打印的是 | |
ba | |
让ba === char *a = "b%s"; | |
要想让等式成立,b必须是这样一种形式: | |
char *a = "... | |
到达b自身的时候我们要小心X的陷阱,实际上b必须停在自身之前,他就等于 | |
b === char *a = "; | |
剩下的就是a, | |
a === b%s" === char *a = "%s" | |
我们展开ba,可以得到 | |
ba === char *a = "char *a = "%s" | |
而, | |
char *a = "b%s" === char *a = "char *a = "%s"; | |
奇迹发生了,他们相等。 | |
在这里,我们让a = b + ...,但同时b = ... + a,在打印的时候,首先b作为格式字符串将自身打印出来了,然后%s被解释出来,用a去替换掉,那么结果就是ba,并且b === a(不考虑那个%s和冒号),也就是说同一个字符串{char *a = }同时作为格式字符串和数据字符串,它具有二义性,并且能够自己描述自己。有个专业术语,自指,说的就是这个。而这个,以我目前的理解来说,就是这段代码能够打印自己的核心和基石:语言能够描述语言自身。(关于这个GEB里面有很专门的一章展开讲,感兴趣的可以移步)。 | |
注意到,利用这种形式,我们可以在char *s前面加任意的代码,然后把b设为这段代码,最终都可以打印出来并且一致。而后面的printf语句似乎也可以用同样的方式构造。 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment