C语言实现Qunie——一个输出和源码完全相同的程序

Qunie是一段没有输入,但输出和它本身源码相同的程序。本文无任何高深技术,纯属娱乐!

最近看到wikipedia的一个词条——Quine,简介部分摘录于此,并简要翻译:

A quine is a non-empty computer program which takes no input and produces a copy of its own source code as its only output. The standard terms for these programs in the computability theory and computer science literature are "self-replicating programs", "self-reproducing programs", and "self-copying programs".
Qunie是一段计算机程序,它没有输入,但输出和它本身的源码相同。在可计算理论和计算机科学领域的标准定义,叫做“自我重复程序”、“自我生成程序”和“自我拷贝程序”。

从上面这段简介中可以看到,Qunie是一段没有输入,但输出和它本身源码相同的程序。

从hello world开始

经典的Hello world程序如下:

#include <stdio.h>

int main()
{
	printf("Hello, World!\n");
	return 0;
}

现在,就从这个程序开始,常识写出Quine。输出源码文本,又有输入,只能用hard-coding一个string array了;一旦开始尝试,你会发现,string array写到下面的时候,写不下去了:

#include <stdio.h>

int main()
{
	int i;
	char* text[] = {
		"#include <stdio.h>",
		"",
		"int main()",
		"{",
		"	char* text[] = {",
		// ...
	};

	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
		printf("%s\n", text[i]);
	}
	// printf("Hello, World!\n");
	return 0;
}

不能继续重复,否则就是“无限递归”,string array就写不完了。现在,只能输出到第6行。

“山里有个庙”的故事是讲不完的

必须想出——如何输出第6行之后的这个string array的内容?回顾一下,printf("%s\n", text[i]);只输出了引号内的内容,加上前缀(" \"", TAB键可以直接写在字符串里面)和后缀("\",")即可,即printf(" \"%s\",", text[i]);

于是,可以继续写:

#include <stdio.h>

int main()
{
	int i;
	char* text[] = {
		"#include <stdio.h>",
		"",
		"int main()",
		"{",
		"	char* text[] = {",
		// ...
	};

	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
		printf("%s\n", text[i]);
	}
	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
		printf("		\"%s\",", text[i]);
	}
	return 0;
}

这样,程序可以输出到第11行了。

第一个看似正确的解答

剩下的源码也是要写到text里,同时要保证——string array的输出在两者中间,于是第一个循环也要被拆为两个部分:

#include <stdio.h>

int main()
{
	int i;
	char* text[] = {
		"#include <stdio.h>",
		"",
		"int main()",
		"{",
		"	char* text[] = {",
		"	};",
		"",
		"	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
		"		printf("%s\n", text[i]);",
		"	}",
		"	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
		"		printf("		\"%s\",", text[i]);",
		"	}",
		"	return 0;"
		"}",
	};

	for(i = 0; i < 6; ++i) {
		printf("%s\n", text[i]);
	}
	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
		printf("		\"%s\",", text[i]);
	}
	for(i = 6; i < sizeof(text)/sizeof(text[0]); ++i) {
		printf("%s\n", text[i]);
	}
	return 0;
}

用工具验证

这个版本就是正确的么?细微的差别,眼睛看起来费劲,且文本比较本来就该是工具干的。
编译:
xusiwei1236@csdn.net:~/test$ gcc quine-printf.c
运行:
xusiwei1236@csdn.net:~/test$ ./a.out > out
比较:
xusiwei1236@csdn.net:~/test$ git diff quine-printf.c out

git diff一比,发现还是有点差别的:

diff --git a/quine-printf.c b/out
index ec3ecf9..d48d2ab 100644
--- a/quine-printf.c
+++ b/out
@@ -13,13 +13,13 @@ int main()
                "       };",
                "       ",
                "       for(i = 0; i < 6; ++i) {",
-               "               printf(\"%s\\n\", text[i]);",
+               "               printf("%s\n", text[i]);",
                "       }",
                "       for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
-               "               printf(\"               \\\"%s\\\",\\n\", text[i
+               "               printf("                \"%s\",\n", text[i]);",
                "       }",
                "       for(i = 6; i < sizeof(text)/sizeof(text[0]); ++i) {",
-               "               printf(\"%s\\n\", text[i]);",
+               "               printf("%s\n", text[i]);",
                "       }",
                "       return 0;",
                "}",

可以看到有三处差异,都是因为转义字符的问题;大体有两类,格式化字符串的开头和结尾的引号,以及前后缀字符串中间的特殊字符。

更正

直接用printf看起来像是没办法,因为不可避免的要用格式化字符串,自然就有引号,对应的字符串里面就会有转义字符。对于第一、第三处的printf("%s\n", text[i]);很容易想到,可以用puts(text[i]);替换,既简单又优雅(对于的字符串也要更改,这里只是对于的代码部分)的解决了格式字符串的开头和结尾的引号(")和换行符(\n)。这样解决了格式化字符的问题。

对于第二处,有前后缀需要拼接,C的标准库的strcat提供了该功能(需要注意strcat要求左边的字符串有足够的空间),于是可以将第二处改为:

		char prefix[128] = "		\"";
		char postfix[] = ",";
		puts(strcat(strcat(prefix, text[i]), postfix));

这样仍然没有解决前后缀中的特殊字符,看到[],我们自然想到,可以直接写成员的值(别拿村长不当干部,别当字符数组不是数组,查一下ASCII码表):

		char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table, 0x22 quote
		char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma
		puts(strcat(strcat(prefix, text[i]), postfix));

好了,现在完美了(__LINE__是行号的预定义宏,为了消除后面6那个magic number,让程序看起来更优雅):

#include <stdio.h>
#include <string.h>

int main()
{
	int i;
	int header = __LINE__ + 1;
	char* text[] = {
		"#include <stdio.h>",
		"#include <string.h>",
		"",
		"int main()",
		"{",
		"	int i;",
		"	int header = __LINE__ + 1;",
		"	char* text[] = {",
		"	};",
		"	",
		"	for(i = 0; i < header; ++i) {",
		"		puts(text[i]);",
		"	}",
		"	",
		"	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {",
		"		char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table",
		"		char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma",
		"		puts(strcat(strcat(prefix, text[i]), postfix));",
		"	}",
		"	",
		"	for(i = header; i < sizeof(text)/sizeof(text[0]); ++i) {",
		"		puts(text[i]);",
		"	}",
		"	",
		"	return 0;",
		"}",
	};
	
	for(i = 0; i < header; ++i) {
		puts(text[i]);
	}
	
	for(i = 0; i < sizeof(text)/sizeof(text[0]); ++i) {
		char prefix[128] = {0x09, 0x09, 0x22, 0}; // 0x09 table
		char postfix[] = {0x22, 0x2C, 0}; // 0x22 quote, 0x2C comma
		puts(strcat(strcat(prefix, text[i]), postfix));
	}
	
	for(i = header; i < sizeof(text)/sizeof(text[0]); ++i) {
		puts(text[i]);
	}
	
	return 0;
}

转载注明原文链接,及出处:http://blog.csdn.net/xusiwei1236
wikipedia的Qunie词条,可以看到更多其他语言版本的Qunie:https://en.wikipedia.org/wiki/Quine_(computing)

posted @ 2020-10-01 18:57  码工许师傅  阅读(608)  评论(0编辑  收藏  举报