我无法理解编写编译器的复杂性是什么,是否有必要编写它?是不是有一个通用的编译器——就像我点击“导入”一样,一切都被自动翻译成机器语言。之所以出现这个问题,是因为互联网上有很多关于这个主题的相互矛盾的信息。
据我了解,需要一个编译器将一些高级语言(例如,C“C”)的程序转换为处理器可以理解的机器代码。但是程序是高级语言中的一组指令,它对应于低级语言中的一个位序列。高级编程语言,粗略地说,不就是低级的“翻译程序”,称为编译吗?
我了解到许多开发公司自己为他们的产品编写单独的编译器。但是是这样吗,如果一个高级语言的命令对应一个低级的命令(当然不同语言都有自己的),理论上你不需要写任何东西(所有应该已经在 30 年前写好了)?
还有在网上他们写说编译语言比解释语言更难,因为用编译语言写了程序后,还需要编译?困难是什么?如果我理解正确的话,那么很长一段时间以来,高级命令与低级位序列的对应关系(当然,每种语言都有自己的对应关系)?或者一切都完全不同,那么在技术上用高级语言编译是什么?
不,不可能有一个通用的编译器,只是因为在任何时候你都可以想出一种现有编译器不知道的带有新关键字的语言。
没错,通常就是这样。(一个例外是中间代码的编译器,例如 C# 编译器。)
是的。而编译器所从事的正是高级指令到低级指令的翻译。
那没有。编程语言通常不包含“编译源代码”指令,这将是一个过于专业的构造。通常可以用编程语言编写编译器,这不是一项简单的任务。即使有这样的指令,那么它的实现也将是编译器的实现,因此编译器将被内置到语言中,但仍然存在。
为了清楚地表明编译任务很复杂,想象一下在 C++ 源文件中查找所有变量声明这样简单的事情。为此,您必须运行预处理器并展开所有宏(每个宏都可以更改代码的含义)。然后,您需要遍历文本并找到所有标识符。它们都是变量吗?但是,有些可能已经定义。好的,您的代码找到了该行
这是指向
t类型的指针的定义T吗?可能是,可能不是。如果T和pt是先前定义的变量,那么这是对乘法运算符的调用。也就是说,您的代码必须知道当前可用的变量,以便了解此行是否是新变量的声明。所以,他必须知道所有函数和所有类的边界。然后,假设我们有
*,我们发现它不是指针声明,而是乘法。使用什么指令?如果这是整数的乘法,在 Intel 架构上您可以使用imul. 如果是浮点乘法,则需要这样的指令mulsd(如果您使用的是 XMM 寄存器)。而如果其中一个数字是浮点数,另一个是整数,那么处理器根本没有这样的乘法指令,需要将整数转换为浮点数。如果您不乘以数字,而是使用重载乘法运算符的用户定义数据结构,那么您需要完全调用函数而不是乘法。我所描述的甚至不代表 C++ 编译器复杂性的十分之一。
新的语言标准和新的语言功能会定期发布,例如 C#,仅今年一年就发布了该语言的三个(次要)版本。旧编译器无法编译新代码。
不,这不对。您不能将关键字机械地映射到
for一组机器代码位并将左括号映射到另一组。如果机器代码的语义对应于所有语言的语义,这将是可能的。例如,C++ 有变量的概念,这在本机代码中是完全没有的。事实上,在块的末尾,从 C++ 的角度来看,变量“消失”不能“直接”用机器代码编码。编译要困难得多。通常你不需要这个:语言的作者通常会为该语言推出一个编译器,如果该语言很流行,那么通常有几个针对不同平台的编译器。仅当您对可用编译器提供的代码质量不满意(目前通常非常好),或者没有人为您的平台编写编译器时,自己编写编译器才有意义。请记住,编写编译器(通常还要实现一种语言的标准库)是一项非常困难的任务。
是和不是。
事实是,在任何现代编译器中,至少可以区分形成管道的三个阶段:
已经观察到,这些部分在不同语言和不同平台之间重复出现。因此,像LLVM这样的项目诞生了,其中这些阶段被实现为单独的可互换模块,以程序代码的单个内部汇编器式表示为中心。
有了这样的编译器组织,您真的不必从头开始编写所有内容。您所要做的就是将您的“渲染器”提供给中间表示(也称为前端),仅此而已。
平台也是如此。有一个新的处理器、操作系统或可执行文件格式——您需要做的就是提供适当的“翻译器”(也称为后端)。
也就是你的前端不关心目标平台(这不是他关心的),而第三方后端也不关心你的语言——中间表示对于所有语言都是一样的,无一例外。