Permalink
Browse files

add 打印自身的程序

  • Loading branch information...
Zibo Wang Zibo Wang
Zibo Wang authored and Zibo Wang committed Jan 28, 2017
1 parent c4dce9c commit 641467f7954a58eb38222eb4e3ff10593464c10c
Showing with 96 additions and 0 deletions.
  1. +96 −0 打印自身的程序.md
@@ -0,0 +1,96 @@
---
title: 打印自身的程序
tags:
- C
- Python
categories:
- 计算机
date: 2017-01-28 21:54:31
---

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

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

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

<!--more-->

* * *

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

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

```C
main(){printf("");}
```

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

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

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

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

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

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

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

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

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

```C
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):

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

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

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

* * *

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

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

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

大家还可以[在GitHub搜索“quine”](https://github.com/search?q=quine),寻找更多类似的好玩项目。

0 comments on commit 641467f

Please sign in to comment.