直观地说,Foo语言的编译器本身似乎不能用Foo来编写。更具体地说,Foo语言的第一个编译器不能用Foo编写,但任何后续的编译器都可以为Foo编写。
但这是真的吗?我隐约记得读过一种语言,它的第一个编译器是用“自身”编写的。这可能吗?如果可能,如何实现?
直观地说,Foo语言的编译器本身似乎不能用Foo来编写。更具体地说,Foo语言的第一个编译器不能用Foo编写,但任何后续的编译器都可以为Foo编写。
但这是真的吗?我隐约记得读过一种语言,它的第一个编译器是用“自身”编写的。这可能吗?如果可能,如何实现?
当前回答
我记得我听过一个软件工程广播播客,其中Dick Gabriel谈到了如何在纸上用LISP编写一个最简单的版本,然后手工将其组装成机器代码,从而引导最初的LISP解释器。从那时起,其余的LISP特性都是用LISP编写和解释的。
其他回答
当你为C编写第一个编译器时,你是用其他语言编写的。现在,你有一个C语言的编译器,比如说,汇编器。最终,您将不得不解析字符串,特别是转义序列。您将编写代码将\n转换为十进制代码10(和\r转换为13,等等)的字符。
编译器准备好后,你将开始用c重新实现它,这个过程被称为“引导”。
字符串解析代码将变成:
...
if (c == 92) { // backslash
c = getc();
if (c == 110) { // n
return 10;
} else if (c == 92) { // another backslash
return 92;
} else {
...
}
}
...
当编译时,你有一个二进制文件,它理解'\n'。这意味着你可以修改源代码:
...
if (c == '\\') {
c = getc();
if (c == 'n') {
return '\n';
} else if (c == '\\') {
return '\\';
} else {
...
}
}
...
那么,“\n”是13的代码的信息在哪里?它是二进制的!这就像DNA:用这个二进制文件编译C源代码将继承这个信息。如果编译器编译自己,它将把这些知识传递给它的后代。从这一点开始,仅从源代码就无法看到编译器将做什么。
如果你想在某个程序的源代码中隐藏病毒,你可以这样做:获取编译器的源代码,找到编译函数的函数,并用这个函数替换它:
void compileFunction(char * name, char * filename, char * code) {
if (strcmp("compileFunction", name) == 0 && strcmp("compile.c", filename) == 0) {
code = A;
} else if (strcmp("xxx", name) == 0 && strcmp("yyy.c", filename) == 0) {
code = B;
}
... code to compile the function body from the string in "code" ...
}
有趣的部分是A和b。A是包括病毒在内的compileFunction的源代码,可能以某种方式加密,所以从搜索结果二进制中不明显。这确保编译到编译器本身将保留病毒注入代码。
B对于我们想用病毒替换的函数是一样的。例如,它可能是源文件“login.c”中的“login”函数,该函数可能来自Linux内核。我们可以将其替换为一个版本,该版本将接受root帐户的密码“joshua”作为普通密码。
如果你编译它并以二进制文件的形式传播,就无法通过查看源代码来找到病毒。
这个想法的最初来源是:https://web.archive.org/web/20070714062657/http://www.acm.org/classics/sep95/
这就是所谓的“自我引导”。你必须首先用其他语言(通常是Java或C)为你的语言构建一个编译器(或解释器)。一旦完成了这一步,你就可以用语言Foo编写一个新版本的编译器。您使用第一个引导编译器来编译编译器,然后使用这个编译器来编译其他所有内容(包括自身的未来版本)。
大多数语言确实是以这种方式创建的,部分原因是语言设计者喜欢使用他们正在创建的语言,也因为非平凡的编译器通常可以作为语言“完整”程度的有用基准。
Scala就是一个例子。它的第一个编译器是用Martin Odersky的实验语言Pizza创建的。从2.0版开始,编译器完全用Scala重新编写。从那时起,旧的Pizza编译器可以完全被丢弃,因为新的Scala编译器可以用于将来的迭代中编译自己。
也许你可以用BNF来描述BNF。
实际上,大多数编译器都是用它们所编译的语言编写的,原因如上所述。
第一个引导编译器通常是用C、c++或Assembly编写的。
Mono项目的c#编译器已经“自托管”很长一段时间了,这意味着它是用c#本身编写的。
据我所知,编译器最初是纯C代码,但一旦实现了ECMA的“基本”特性,他们就开始用c#重写编译器。
我不知道用同一种语言编写编译器有什么好处,但我确信它至少与语言本身可以提供的特性有关(例如,C不支持面向对象编程)。
你可以在这里找到更多信息。