能够输出自己的程序
我们都写过这个程序:
它会输出:
于是很自然地,要写出一个输出自己的程序,可以尝试这样写:
main() { printf("#include<stdio.h>\nmain() { printf(\"#include<stdio.h>\n main() { ...\") } ");}
它会输出:
你可以继续这样写下去,但你会发现自己已经陷入死循环了,写到printf那就得返回去加上一段。显然像上面这样是做不到的。是不是说这样的程序就不存在呢?答案是否定的,不仅写得出来,而且各种各样,五花八门的都有。
一个很容易想到的办法是利用文件操作,像下面这样:
int main()
{
FILE *fp;
char ch;
fp=fopen("self.c","r");
ch=getc(fp);
while(ch!=EOF){
putchar(ch);
ch=getc(fp);}
return 0;
}
其中self.c中就是本程序的源代码,这显然是可以做到的。
还有一种方法就是调用操作系统本身的功能,在Windows下可以这样:
int main()
{
char path[255];
sprintf(path,"cmd.exe /c type \"%s\"",__FILE__);
system(path);
return 0;
}
如果是在Unix或者Linux下,你可以调用终端,把type换成cat就行了。
如果我们只有可执行的程序而没有源文件,上面的方法就不行了(当然后面一种方法还是可以的,但利用了type,cat等外部程序)。那么怎样写出一个这样的程序,它不依赖外部文件或程序,而可以输出自己呢?
在网上找了一下,才发现其实有很多这样的程序。
原来我想得到的这种程序有个正式名称:Quine,这是由于logician Willard van Orman Quine这位老兄最先提出了这个概念。想想看,其实只要是一个完整的程序设计语言都可以实现这样的功能,因为任何图灵机都可以实现复制自我,程序设计语言也是一种图灵机,当然可以了。但我怎么也想不出,开始甚至觉得是否是不可能的。
第一种方法是把代码当作字符串处理
我觉得最简单清楚又能说明问题的代码是下面这样的:
main() { int j; putchar(99); putchar(104); putchar(97); putchar(114); putchar(32); putchar(120);putchar(91); putchar(93); putchar(61); putchar(34); for(j=0; j<strlen(x); ++j) putchar(x[j]);putchar(34); putchar(59); putchar(10); for(j=0 ; j<strlen(x); ++j) putchar(x[j]); putchar(10); }
但是上面的代码要求你只用两行写出来,第一行是对x的定义,第二行是main;x包含了下面main的代码。程序是这样执行的:
首先开始的10个putchar输出:
第一个循环输出字符串的内容:
接下来的3下putchar输出:
并换行。
写这种程序的关键就是“用来保存后面代码的字符串也要被输出“。
在C语言中,我们知道,用字符串表示完整的格式是很麻烦的,而且如果有换行字符串就不能保持原型了。这样为了避免上面程序必须写在两行的问题。
于是又有下面这种方法:
char c[] = {0x7d,0x3b,0xa,0x69,0x6e,0x74,0x20,0x6d,0x61,0x69,0x6e,0x28,0x29,0xa,0x7b,0xa,0x20,0x20,0x20,0x20,0x70,0x72,0x69,0x6e,0x74,0x66,0x28,0x22,0x23,0x69,0x6e,0x63,0x6c,0x75,0x64,0x65,0x20,0x3c,0x73,0x74,0x64,0x69,0x6f,0x2e,0x68,0x3e,0x5c,0x6e,0x63,0x68,0x61,0x72,0x20,0x63,0x5b,0x5d,0x20,0x3d,0x20,0x7b,0x22,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x66,0x6f,0x72,0x28,0x69,0x6e,0x74,0x20,0x69,0x3d,0x30,0x3b,0x69,0x3c,0x73,0x69,0x7a,0x65,0x6f,0x66,0x28,0x63,0x29,0x3b,0x2b,0x2b,0x69,0x29,0xa,0x20,0x20,0x20,0x20,0x7b,0xa,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x70,0x72,0x69,0x6e,0x74,0x66,0x28,0x22,0x30,0x78,0x25,0x78,0x2c,0x22,0x2c,0x63,0x5b,0x69,0x5d,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x7d,0xa,0x20,0x20,0x20,0x20,0x70,0x72,0x69,0x6e,0x74,0x66,0x28,0x22,0x25,0x73,0x22,0x2c,0x63,0x29,0x3b,0xa,0x20,0x20,0x20,0x20,0x72,0x65,0x74,0x75,0x72,0x6e,0x20,0x30,0x3b,0xa,0x7d,};
int main()
{
printf("#include <stdio.h>\nchar c[] = {");
for(int i=0;i<sizeof(c);++i)
{
printf("0x%x,",c[i]);
}
printf("%s",c);
return 0;
}
看出来了吗?其实这个程序中把从数组结束时的};开始到最后都转换成16进制的ASCII码(用一般的文本编辑器就可以了)保存在c中,然后先以16进制方式输出一遍,又以字符串方式输出一篇,也可以完成输出自己;而且这样做可以保持一个比较好看点的格式了。
总结一下,上面的程序有下面这种形式:
Print(C)”
Print(C=”);Print(C);Print(“)
Print(C)
其实用的比较多的还有利用printf的格式输出:
把上面的代码写在一行上;其中34和10分别是”(双引号)和换行符的ASCII码。看到它是怎样巧妙地把a这个字符串输出的吧。由于a中有%s,利用printf(a,a)这种格式输出a。
下面这个程序和上面一样,但是更简洁:
下面是一种利用宏的方法:
把宏代进去,可以发现其实和上面利用格式输出并没有本质的区别。
最后来欣赏一位牛人写的程序吧。
下面这个程序不仅可以输出自己,而且它代码本身还是回文的:
见到许多能输出自己的各种语言的代码。
2009年4月10日 08:34
这个也可以
#include <stdio.h>
int main() { char self[] = "#include <stdio.h> %cint main() { char self[] = %c%s%c; printf(self, 10, 34, self, 34); return 0;}"; printf(self, 10, 34, self, 34); return 0;}
2009年4月10日 11:37
@bay__gulf618: 是的,上面也讲到了这种方法。
2009年12月13日 01:57
我还是倾向于那个 通过os 打印当前文件的方式
2009年12月13日 11:54
@皮贝贝: 但是好像不够通用。