原文:传送门
或者:传送门
编译器的前后端
编译器以中间代码为界限,又可以分前端和后端。比如 clang 就是一个前端工具,而 LLVM 则负责后端处理。另一个知名工具 GCC(GNU Compile Collection)则是一个套装,包揽了前后端的所有任务。前端主要负责预处理、词法分析、语法分析,最终生成语言无关的中间代码。后端主要负责目标代码的生成和优化。
自举
不知道有没有人思考过,C 语言的编译器是如何实现的?实际上它还是用 C 语言实现的。这种自己能编译自己的神奇能力被称为自举(Bootstrap)。
乍一看,自举是不可能的。因为 C 语言编译器,比如 GCC,要想运行起来,必定需要 GCC 的编译器将它编译成二进制的机器码。然而 GCC 的编译器又如何编译呢……
解决问题的关键在于打破这个循环,我们可以先用一个比 C 语言低级的语言来实现一个 C 语言编译器。这件事是可能做到的,因为这个低级语言必然会比 C 语言简单,比如我们可以直接用汇编代码来写 C 语言的编译器。由于越低级的语言越简单,但表达能力越弱,所以用汇编来写可能太复杂。这种情况下我们可以先用一个比 C 语言低级但比汇编高级的语言来实现 C 语言的编译器,同时用汇编来实现这门语言的编译器。总之就是不断用低级语言来写高级语言的编译器,虽然语言越低级,它的表达能力越弱,但是它要解析的语言也在不断变简单,所以这件事是可以做到的。
有了低级语言写好的 C 语言编译器以后,这个编译器是二进制格式的。此时就可以删掉所有的低级语言,只留一个二进制格式的 C 语言编译器,接下来我们就可以用 C 语言写编译器,再用这个二进制格式的编译器去编译 C 语言实现的 C 语言编译器了,于是完成了自举。
以上逻辑描述起来比较绕,但我想多读几遍应该可以理解。如果实在不理解也没关系,我们只要明白 C 语言可以自举是因为它可以编译成二进制机器码,只要用低级语言生成这个机器码,就不再需要低级语言了,因为机器码可以直接被 CPU 执行。
从这个角度来看,解释型语言是不可能自举的。以 Python 为例,自举要求它能用 Python 语言写出来 Python 的解释器,然而这个解释器如何运行呢,最终还是需要一个解释器。而解释器体系下, Python 都是从源码经过解释器执行,又不能留下什么可以直接被硬件执行的二进制形式的解释器文件,自然是没办法自举的。然而,就像前面说的,Python 完全可以实现一个编译器,这种情况下它就是可以自举的。
所以一门语言能不能自举,主要取决于它的实现形式能否被编译并留下二进制格式的可执行文件。
胶水语言 Python
Python 一个很强大的特点是胶水语言,可以把 Python 理解为各种语言的粘合剂。对于 Python 可以处理的逻辑,用 Python 代码即可完成。如果追求极致的性能或者调用已经实现的功能,也可以让 Python 调用已经由别的语言实现的模块,以 Python 和 C 语言的交互解释一下。
首先,如果是 C 语言要执行 Python 代码,显然需要一个 Python 的解释器。由于在 Mac OS X 系统上,Python 解释器是一个动态链接库,所以只要导入一下头文件即可,下面这段代码可以成功输出 “Hello Python!!!”:
include
import
int main(int argc, const char * argv[]) {
Py_SetProgramName(argv[0]);
Py_Initialize();
PyRun_SimpleString("print 'Hello Python!!!'\n");
Py_Finalize();
return 0;
}
如果是在 iOS 应用里,由于 iOS 系统没有对应的动态库,所以需要把 Python 的解释器打包成一个静态库并且链接到应用中,网上已经有人做好了: python-for-iphone,这就是为什么我们看到一些教育类的应用模拟了 Python 解释器,允许用户编写 Python 代码并得到输出。
Python 调用 Objective-C/C 也不复杂,只需要在 C 代码中指定要暴露的模块 A 和要暴露的方法 a,然后 Python 就可以直接调用了:
import A
A.a()
详细的教程可以看这里: 如何实现 C/C++ 与 Python 的通信?
有时候,如果能把自己熟悉的语言应用到一个陌生的领域,无疑会大大降低上手的难度。以 iOS 开发为例,开发者的日常其实是利用 Objective-C 语法来描述一些逻辑,最终利用 UIKit 等框架完成和应用的交互。 一种很自然而然的想法是,能不能用 Python 来实现逻辑,并且调用 Objective-C 的接口,比如 UIKit、Foundation 等。实际上前者是完全可以实现的,但是 Python 调用 Objective-C 远比调用 C 语言要复杂得多。
一方面从之前的分析中也能看出,并不是所有的源码编译成目标文件都可以被 Python 引用;另一方面,最重要的是 Objective-C 方法调用的特性。我们知道方法调用实际上会被编译成 msg_Send
并交给 runtime 处理,最终找到函数指针并调用。这里 Objective-C 的 runtime 其实是一个用 C 语言实现动态链接库,它可以理解为 Objective-C 运行时环境的一部分。换句话说,没有 runtime 这个库,包含方法调用的 Objective-C 代码是不可能运行起来的,因为 msg_Send
这个符号无法被重定向,运行时将找不到 msg_Send
函数的地址。就连原生的 Objective-C 代码都需要依赖运行时,想让 Python 直接调用某个 Objective-C 编译出来的库就更不可能了。
想用 Python 写开发 iOS 应用是有可能的,比如: PyObjc,但最终还是要依赖 Runtime。大概的思路是首先用 Python 拿到 runtime 这个库,然后通过这个库去和 runtime 交互,进而具备了调用 Objective-C 和各种框架的能力。比如我要实现 Python 中的 UIView
这个类,代码会变成这样:
import objc
这个 objc 是动态加载 libobjc.dylib 得到的
Python 会对 objc 做一些封装,提供调用 runtime 的能力
实际的工作还是交给 libobjc.dylib 完成
class UIView:
def init(self, param):
objc.msgSend("UIView", "init", param)
这么做的性价比并不高,如果和 JSPatch 相比,JSPatch 使用了内置的 JavaScriptCore 作为 JavaScript 的解析器,而 PyObjc 就得自己带一个 libPython.a 解释器。此外,由于 iOS 系统的沙盒限制,非越狱机器并不能拿到 libobjc 库,所以这个工具只能在越狱手机上使用。