Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
97 lines (66 sloc) 4.3 KB
title tags categories date
打印自身的程序
C
Python
计算机
2017-01-28 13:54:31 -0800

如何写一个打印自身源代码的C程序呢?或者说,self.c这个文件的内容应该是什么,才能让以下命令不产生任何输出?

gcc self.c &>/dev/null && \
  ./a.out >self.txt && \
  diff self.c self.txt

当然了,请不要使用读取文件之类的手段,而且self.c越小越好。


首先需要说明一下,不写#include <stdio.h>是可以编译通过的,程序中用到的库函数会按C的规则被自动推断类型。省略main函数的int类型也是合法的。为了使程序短小,我们忽略这些东西。我们也不写文件末尾的换行符(EOL)。

先试试直接用printf函数输出:

main(){printf("");}

把要输出的内容,也就是程序源代码本身填入字符串,我们得到了:

main(){printf("main(){printf(\"\");}");}

但是这样填入后,程序源代码本身又产生了变化,打印的内容不再是源代码本身了。不难理解,这个思路根本就是不行的,printf的参数一定比程序本身短,那又怎么可能等于程序本身呢?

不过不要急,我们从上面那段程序出发,再修正一下字符串的内容:

main(){printf("main(){printf(\"main(){printf(\\\"\\\");}\");}");}

这段程序还是错误的,但能够看出规律了——假如我们把main(){printf(称作一个“片段”,那么粗略地说,上面这段程序包含3个片段,却只能打印出2个片段。怎样才能在不增加源代码中片段数的前提下多打印出1个片段呢?用变量可以解决这个问题。

如果我们把一个片段作为字符串存储进变量中,就可以不必重复写出这个片段,而把它打印任意次了,比如:

char*s="main(){printf(";
main(){printf("%s%s%s%s",s,s,s,s);}

这段代码只包含2个片段,却能打印出4个片段,打印的内容不再少于源代码的内容了,离成功近了一步。思路的关键在于:如果要出现重复内容,不要把它重写一遍,而应该用变量来替代它!基于这个思路,可以写出下面这个版本:

char*s="char*s=\"%s\";\nmain(){printf(s,s);}";
main(){printf(s,s);}

这段代码中,字符串s就是要打印的内容,它应该包含自身,但我们在那里放了一个%s,打印时由printf函数将它代入,这样就避免了“s自己包含自己”的困境。这个版本看起来应该能满足要求了,是吗?这是它的输出:

char*s="char*s="%s";
main(){printf(s,s);}";
main(){printf(s,s);}

为什么会这样?如果仔细对比一下这个输出和源代码,不难发现问题是\"被(错误地)打印为了"\n被(错误地)打印为了真正的换行符。那该如何打印出\"呢?你要在源码中写\\\"。可是这样写的话你就面临着如何打印出\\\"的问题,又不得不在源码中写\\\\\\\"……结论显然:我们就不应该使用转义字符,它们没法被原样打印。

避免转义字符很简单,在需要特殊字符(也就是"和换行符)的地方放%c,并在printf中提供相应字符的ASCII编码(分别是34和10):

char*s="char*s=%c%s%c;%cmain(){printf(s,34,s,34,10);}";
main(){printf(s,34,s,34,10);}

这个程序已经符合要求啦!但是它还可以更短一些,通过写成一行,以及更简易地声明s。最短的版本长度为64字节,它是这样的:

main(s){printf(s="main(s){printf(s=%c%s%c,34,s,34);}",34,s,34);}

结尾再放两个小玩意,一个是我写的打印自身的Python程序(可以看出,此类程序的结构很相似):

s='s=%r;print(s%%s)';print(s%s)

另一个是国外大神制作的100种编程语言构成的圈,这些编程语言按字典顺序排列,每种语言的那段程序输出后一种语言的一段程序代码,最后一段程序输出第一个语言的程序代码:github.com/mame/quine-relay。顺便一提,第一个程序用等宽字体显示时,远看是一幅图片,描绘了一条咬住自己尾巴的蛇。

大家还可以在GitHub搜索“quine”,寻找更多类似的好玩项目。