语言、编译、运行

created_at: 2023-01-26


这是一篇引子,这里不会深入解释各种概念,会在后续的文章里。出于本人的认知水平的原因,文章本身可能会有不准确的地方会不断修改完善。

语言分类

通常情况下,编程语言一般被归类为解释型和编译型,但是这种分类其实无法合理的解释清楚编程语言本身的原理。例如 Java 虽然不是直接编译成机器码执行,但仍需要编译成其 VM 支持的 bytecode 执行,又比如,Python 虽然被归类为解释型语言,但是也存在 pyc 格式的 bytecode,并且可以直接执行。所以从执行原理上,可以分为在特定的 VM 上执行的方式和直接执行原生机器码的方式。但是这种表述依然并不准确,因为机器只能理解自己设定的机器码,即使是使用 VM 抽象出来的指令,最终还是要还原成特定的 CPU 架构能理解的指令才能执行。

这里说的 VM,指的是程序虚拟机而非系统虚拟机。

总之,每种编程语言的运行和实现在技术逻辑上是一致的,但是实现的细节上又各不相同。

从程序最终的呈现形式上说,可以直接分为 elf 格式的可执行文件或者其他任何格式但需要依赖于 VM 或者解释器的语言。这里不得不提一下即时编译,基本上现在解释型语言都在使用 JIT 的方式提高运行速度。

xml、html、json、yaml、markdown 这些标记语言也被称为非图灵完备语言,他们都是描述语言,本身并不能用来做运算。一般在使用过程中,同样会有词法扫描和语法扫描的阶段,但是后续的转译阶段一般是没有的。

bash 一般被称为命令处理器,因为是作为操作系统的接口来使用,但其实也算是图灵完备的语言。

编译过程

一般来说,不管是编译成 elf 执行的语言(为了简单以下统称为编译型)还是 VM 上执行语言(统称为解释型,虽然可能不太准确),在开始进入编译前都会经历以下几个阶段:

  • 词法扫描;
  • 语法扫描;
  • 语义分析;

在上述步骤进行完生成 AST 之后,两者在这之后开始分化,解释型语言这时候只需要生成对应的自定义指令然后再 VM 上执行即可,而编译型语言后续的流程一般是:

  • 中间代码生成
  • 代码优化
  • 生成机器码

这之后还会有 elf 文件组装的过程。

内存管理

从 C 入门开始,大家都知道栈区内存不需要手动管理,而堆区内存需要手动管理。C++也是如此。手动管理内存是一件很麻烦的事情。

不需要手动管理内存的语言,一般会使用 gc 的方式来实现,gc 有多重实现方式,最常见的就是引用计数法和标记清除法,如果想深入了解 gc,推荐阅读《垃圾回收的算法与实现》

Rust 这几年异军突起,打的就是既没有 gc,又不需要手动管理内存的牌。基于上面两个预设条件,rust 在使用上许多特性和其他语言差异很大,如变量的作用域,以及 borrow 的概念。

Go 涉及到的知识点

稍微了解过一些 go 内部实现的基本上都知道 go 使用的是 plan9 的汇编语言,但是显然 plan9 的汇编没有在任何主流的 cpu 上使用,所以也就无法直接转译为任何主流 cpu 的机器码,这中间势必会有一个转译的过程。

同上,大家都知道 go 实现的自举,也就是整个编译器完全由 go 和汇编实现,但是操作系统本身不提供 go 的 api,而如果阅读 go 的 net 模块的代码,会发现 go 的 tcp 等网络协议还是通过系统调用来实现的,这种调用离不开操作系统的 Syscall。

go 不需要手动管理内存,所以 go 有 gc,采用的是三色标记法+混合写屏障的方式。go 的 gc 过程会暂停程序的执行,所以称为 Stop the world(stw)。

WebAssembly

WebAssembly 本质上是一种通用形式的虚拟机,wasm 并不会完全取代 js,而是作为 js 的一种补充存在。

因为 Bytecode Alliance 的存在,wasm 的应用的边界正逐渐跃出浏览器的范围。现阶段 wasm 在云上的应用还没有铺开,但是已经涌现出了几个 runtime,未来可期。

对一些现有的脚本语言来说,也许可以通过这种方式打通和其他语言之间的边界。

对未来的一些预测

  • Rust 与 Go 的占比会越来越大,一个偏向底层,一个偏向应用;
  • WASM 的应用范围会越来越广;
  • JavaScript 依旧会长期存在,因为生态已经形成转向需要很长时间;
  • 编程语言会越来越多,越来越成熟,因为基础组件的能力越来越强、越来越完善,实现一个编程语言会越来越简单;
  • 构建一个编程语言的生态系统依旧很难,可能未来的主要应用广泛的编程语言都由大公司或者大公司组成的联盟设计的;
  • 也许会出现 Go 的替代。