Skip to content

Instantly share code, notes, and snippets.

@ichaos
Last active December 16, 2015 17:50
Show Gist options
  • Save ichaos/5473364 to your computer and use it in GitHub Desktop.
Save ichaos/5473364 to your computer and use it in GitHub Desktop.
self print program
可以编写一个程序打印出自身的代码么?怎么写呢?为什么呢?
首先,当然是可以的,我们可以打开保存源代码的文件,把文件内容打印出来,当然就是代码本身了。但是,这里实际上程序和代码是分离的,不能算严格意义上的自我表示,二进制代码在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