从程序与机器码看低代码演进方向
引言
“低代码”并不是一个新概念,但是却反复的被提起。编程语言设计者总是想通过一些简单的方法,解决一些复杂的问题,使一项技术的门槛不断降低,受众不断扩大。通过编程语言的抽象,逐步简化编程,使编程难度由一个复杂量级,缩小了很多倍,大大缩短了开发周期,试想,如果用汇编语言开发一个现在的企业信息化管理系统,将需要写上多少代码,这些代码的维护难度又将何其之高。在高级编程语言中,通过语法、语意的抽象,使代码以更加简洁易懂的格式呈现出来,对计算机行业的推进,起到了极大的作用。
当我们用高级语言进行编程时,机器屏蔽了程序的机器级实现。汇编程序员必须指定程序的低级指令来让计算机执行特定的计算任务。高级语言提供的抽象级别较高,大多数时候在这种抽象级别上工作效率会更高,也更可靠。编译器提供的类型检查能帮助我们发现许多程序错误,并能够保证按照一致的方式来引用和处理数据。通常情况下,现代的优化编译器产生的代码至少与一个熟练的汇编语言程序员手工编写的代码一样有效。最大的优点是高级语言编写的程序可以在很多不同的机器上编译和执行,而汇编代码则是与特定的机器密切相关的。
机器级别的代码
下边我们通过编写一段 c 程序 code.c 为例展示其机器级别代码
通过 GCC 编译汇编之后:
生成 code.o 二进制文件,其以 16 进制打开后展示为:
这种机器级别的代码,想要读懂就比较困难,而且不同的机器编译汇编后的结果也不相同。本次编译环境为:Intel Core I5 8200b,MacOS 10.15.7,Apple clang version 12.0.0 (clang-1200.0.32.28)
汇编代码
通过反汇编工具,将 code.o 逆向出来则其汇编代码如下:
高级语言与低级别代码的比较
从二进制指令到汇编指令,将机器级代码的格式和行为用可读性更好的文本格式来表示,比如指令集体系(ISA),指令按一定顺序执行。形成固定的处理器指令执行模型。
高级编程语言(比如 C 语言)通过编译转换方式,将复杂的指令调用抽象成指定的语法编码组织形式。使得编程复杂度进一步简化。编译器帮助开发者完成了大部分工作,程序员无需关注处理器底层的状态信息:程序计数器、寄存器文件、条件码寄存器、浮点数寄存器等。
几种指令集体系(Instruction set architecture:ISA)
IA-32
IA-32 (Intel Architecture, 32 位),也被称为 x86-32、i386 或 x86,是英特尔最成功的商业微处理器的 CISC 指令集体系结构,并首次在 Intel 80386 中实现,作为 x86 体系结构的 32 位扩展。这种体系结构为目前安装在世界上大多数个人计算机上的微处理器家族定义了指令集,尽管它现在正在被 x86-64 所取代。
x86-64
x86-64 是由 AMD 开发的 64 位处理技术,首次使用 Opteron 和 Athlon 64 处理器。x86-64 也被称为 x64 和 AMD64。
x86-64 支持 64 位处理优势,如增加的内存空间(高达 256TB)和每个时钟周期处理更多数据。
ARMv8 A64
早在 2007 年,ARM 公司已经开始了 64 位架构的研发;2011 年 ARM 官方公布了第一套 64 位处理器架构“ARMv8”,并于当年 11 月签署了第一份授权协议。2012 年 10 月,ARM 公司发布了第一款基于 64 位架构的处理器核心“Cortex-A50”系列,该系列首批包括 Cortex-A57 和 Cortex-A53 两款型号,而这两款可以单独工作,也可以以 big.LITTLE 的形式协同工作。
本文后续如果未做特殊说明,都将采用 intel 的 IA-32 指令体系作为举例。
指令的归类
数据格式
由于 Intel 指令体系由 16 位体系结构扩展而来,Intel(word)用术语“字”表示 16 位数据类型。“双字”(double words) 为 32 位数据类型。“四字”为 64 位数据类型。
数据传送指令
将数据从一个位置复制到另一个位置的指令是最频繁使用的指令。
在 IA32 中,传送指令的两个操作数不能都指向存储器位置。
算数与逻辑操作
控制
条件码
CF:进位标志,最近操作使最高位产生了进位
ZF:零标志,最近的操作得出的结果为 0
SF:符号标志,最近的操作得到的结果为负数。
OF:溢出标志。最近的操作导致一个补码溢出,正溢出或负溢出
条件码的变动,会在算术与逻辑操作时发生。也可已通过 CMP 指令,根据两个操作数的差来设置。另一个就是通过 TEST 指令。
通常条件码不会单独读取使用,而是跟其他场景组合使用:
可以根据条件码与 SET 指令组合将一个字节设置成 0 或者 1
可以跟跳转指令组合跳转到程序的某个其他部分
可以有条件传送数据
跳转指令
正常执行的情况下,指令按照他们出现的顺序一条一条地执行,跳转(jump)指令会导致执行切换到程序的指定的位置。在汇编代码中,这些跳转的目的地会用一个标号标明。比如下边的 L1
跳转指令也会组合条件进行,比如 je 指令则会结合 ZF 条件码,js 指令组合 SF 条件码执行跳转。
循环
汇编中,没有相应的循环指令,需要通过条件测试和跳转组合来实现循环的效果。
do-while 循环翻译形式:
while 循环翻译形式:
for 循环的通用形式:
循环举例:
总结
以上只列举了一部分主要指令,还有条件传递指令、switch 语句等指令,不一一列出。相信通过代码的比对,你能看出高级语言对计算机指令的抽象加工。不同机器的编译器,将高级语言编译汇编成适合本地主机执行的机器级代码,实现跨平台的能力。当然,程序的除了控制逻辑等操作,还需要数据结构,在计算机中,高级语言同样对数据结构做了抽象的简化。
从机器代码到汇编层次的展示,实现了可读性的提升。从汇编发展到高级编程语言,跨越了更高层次的一次提升。由此,我们可以推测未来的编程仍会朝着更简化的方向发展,至于是出现更简洁的语言,还是基于一种语言实现再高一层的抽象,这个有待于完善的理论支撑,目前我还没找到完善的可供指引的理论支持,或者说更易用的切实计算机语言。因此,我们虽然见到一些低代码的实现,却都是很小范围的应用。比如少儿编程平台 scratch.以及一些通过 xml 配置生成代码的工具等。但是大家在用的仍然是主流的编程语言。
再者,我们畅想一下低代码的实现,抽象控制层面(行为抽象),数据层面,仍然需要围绕底层一级的指令归纳与封装展开。此处由于个人学识有限,目前只聊到这里,大家如果有更好的看法,欢迎在留言区写下你的看法。
参考资料
[美] Randal E.Bryant / [美] David O' Hallaron 著 龚奕利 / 雷迎春 译 :深入理解计算机系统 (原书第 2 版)
https://developer.arm.com/architectures/instruction-sets/base-isas/a64
版权声明: 本文为 InfoQ 作者【Lin】的原创文章。
原文链接:【http://xie.infoq.cn/article/3d0e36a38343f38ee06f485ed】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论