main.c -> compiler -> exe
我们通常认为编译器是一个,它接收源代码并将其转换为计算机可以理解的指令,从而生成可执行文件。
但实际上,编译器的设计方式要模块化得多,这项工作并非一蹴而就,而是分为几个阶段,例如 GNU 编译器集合在 编译C代码时分为四个阶段。
Pre-Processor Compiler Assembler Linker
预处理器负责删除注释,解析包含指令和展开宏。 main.h main.c mylib.h mylib.c -> main.c mylib.c
编译器将预处理后的C代码翻译成汇编代码文件。
Compiler main.s mylib.s Assembler。
汇编器将这些汇编文件转换成包含机器指令的目标文件
Assmbler main.o mylib.o Linker
链接器将所有这些目标文件和库文件组合成最终的可执行文件
main.o mylib.o Linker main.exe
由于这种模块化设计,我们不需要用同一种语言编写所有内容。
例如,我们可以用C语言编写项目的大部分代码,用汇编语言编写一些对性能要求较高的部分。 然后在适当的阶段将这些文件传递给编译器
main.c -> PreProcessor -> Compiler -> mylib.s(ASM) main.s -> Assembler -> Linker -> main.exe
这种模块化设计意味着工具链的每个阶段都可以接受来自完全不同来源的输入, 只要输出格式与下一个阶段所期望的格式相匹配即可。所以我们完全可以混合不同的语言。
比如C和Rust,我们可以编译Rust部分,并告诉Rust编译器生成库、目标文件或静态归档文件,而不是独立的可执行文件。
rustc –crate-type=staticlib is_prime.rs
is_prime.rs ->Parser-Expand-Resolve -> Compiler(HIR) -> Type Check -> Compiler(MIR) -> Borrow Checking -> CodeGen(LLVM) -> Assembler(LLVM) -> Linker -> libis_prime.a
gcc main.c libis_prime.a -o count_primes
count_primes.c -> Pre-Processor -> Compiler -> Assembler -> libis_prime.a(From Rust)、count_primes.o -> Linker -> main.exe
最终得到一个由多种编译语言构建而成的单一可执行文件。
这里的问题是,解释型语言如何融入一需要目标文件和可链接二进制文件的流程中?
python.exe main.py
excel.exe data.xlsx
VLC.exe 05_08_2015.mp4这样解释 解释型语言是什么东西 非常合适。
解释器本身并不神奇,它只是一个你磁盘上的一个可执行文件,这意味着它在某个时候是由源代码编译而来的。 所有用于解析脚本、评估表达式、处理变量、类、类型以及语言其他所有特性的代码都是用另一种语言编写的,一种编译型语言。
例如,官方的Python解释器是用C语言写的。CPython。流行的JavaScript运行时Node.js是用C++编写的。
所以当你运行 node main.js 时,你并没有创建 一个 JavaScript进程,正在启动一个C++进程,该进程会读取您的JavaScript代码并进行解释。
当你运行 Python main.py 时,你并没有创建一个Python进程,正在运行一个C程序,该程序读取您的Python脚本并对其进行解释。
编译语言程序使用脚本,将解释器(脚本语言虚拟机)嵌入到自己的编译语言程序中,例如 我们有一个客户端程序,UI有背景色,比如嵌入lua,用lua写背景色,我们C++程序从lua vm中读取 某个变量,用变量值读背景色然后显示,这样我们改背景色时只需要改脚本 非常方便不需要改C++代码 然后在进行编译,就像配置文件一样再用。 但不仅仅当作配置文件功能来用,还可以用脚本写函数,然后我们C++代码用解释器来运行脚本函数,传递参数,获得返回值等,游戏服务器或游戏客户端引擎中经常会嵌入lua,python,js等。 可以做到热更新,修复业务逻辑代码,不停止进程情况下修复BUG。
例如GoDot
Physics Engine、Editor Framework、Networking System、File System、Audio Engine、Input System、Main Loop、 Animation System、UI System、Scene Tree & Node System、Rendering Engine、Scripting Engine 都是由C++写的。
Scripting Engine 是一个 Interpreter 来解释运行 GDScript。
开发者可以使用名为godotscript的解释型语言编写代码,来控制玩家的位置、重力、敌人行为、以及基本上所有的游戏逻辑,而无需重新编译引擎。
从编译型语言调用解释型代码需要嵌入解释器,相反的做法 通过嵌入编译器,无法从解释型语言调用已编译的 代码。
相反,从解释型项目中调用已编译代码通常通过一种称为 Foreign Function Interface 外部函数接口的技术来实现的。
这意味着你在编译型语言中声明函数,而解释型语言能够像调用本地函数一样调用它们。
怎么写外部函数,一般都是脚本解释器定好的规则,然后按解释器定好的规则搞成动态库 .so ,然后在脚本中去加载它们,解释器程序可以识别动态库中有哪些东西,要做什么,将变量、函数、类型 加载到程序中,脚本运行时去调用。
另外还有两种方法可以混合使用解释型语言和编译型语言。
一种方法是使用转译器
mylib.py -> transpiler -> mylib.c
+
main.c -> Compiler -> main.exe另一种方法是使用独立的模块,这些模块在运行时通过IPC(如套接字或管道)进行通信,这其实不算语言混用, 它只是两个独立的程序在互相通信,但从技术上,这是可行的。