2. 编程语言发展史与核心概念

1 语言发展

在第一章中,我们大致了解了计算机的基本组成部分,从较为宏观的角度去理解计算机的原理。

而本章,便开始进入主题,也就是我们如何通过编程去控制计算机实现我们想要让其完成的工作,真正认识编程,快速领略编程语言的发展之旅。

虽然前面我们一直在说,计算机只认识0和1这样的数字、并去执行它,但相信你其实依旧感受的不是特别深。

毕竟CPU作为只接受0和1这样的数字来执行命令的器件,怎么就能够完成如此庞大的计算量呢?

我们便常常能看到指令集这个词,它是一颗CPU所能完成计算的功能集合。而机器码,便是这些指令集所对应的二进制形式,已经固定在了CPU内部,属于焊死在硬件上了。

机器码形象点来说,指的就是机器所能识别的编码,大多数时候这是固定写死在CPU内部的,只要你输入相应的编码,那么CPU就会对其进行执行、并将结果输出。

比如,如果我们想要完成一个加法操作,它对应的机器码可能为:

b0 00 
b1 01
01 c3
8a 00
89 00
b8 01 00 00 00
31 db
cd 80

上面便是CPU执行一个加法功能操作所看到的所有指令所对应的机器码,可以看到,我们作为人来说,实际上是很难看懂上面这串机器码是在干什么的。

实际上还会更复杂,这里用了十六进制代替二进制、并且还分行显示各个机器码的执行开始与结束,实际上CPU看到的是上面十六进制转换为二进制、并且全部放在一行中的数据。

比如一个基本的加法操作,对应的机器码实际上是上面的第三行:01 c3

前面的01代表当前执行的是加法操作,而c3则是代表加法操作所需要的两个参数的位置。

虽然这里看起来它只是一个十六进制的数字,但实际上在CPU眼中,它是一串8位的二进制串,通过预先确定的规则(比如前四位是第一个参数的位置,后四位是第二个参数的位置),就能用一个数字表达很多意思,一个8位二进制串最多能表达28=2562^8=256个信息。

但这还只是基本,另一个复杂的点是,CPU并不是一家独大。

当前世界上的CPU主要分为两类,其一是英特尔,其二是ARM,加上国产CPU目前也在提上进程,种类繁杂,只要CPU的类型不同,那么它们执行同一个操作(比如加法指令)所对应的机器码就不一定相同。

这就意味着,如果你想要写一份通用的程序让其可以放在各种类型电脑上跑起来,那么你还得写多份机器码。

1.1 汇编语言

从上面可以看出来,机器码对于人来说是非常不友好的,我们很难看懂那是在干什么。

为了解决这个问题,于是乎就出现了相应的汇编语言,它与机器码是一对一等价的,其唯一的作用就是让我们能够快速看懂、并且能够方便编程。

比如上面那段机器码,其所对应的汇编代码如下:

b0 00         ; mov al, 0
b1 01         ; mov bl, 1
01 c3         ; add al, bl
8a 00         ; mov al, [result]
89 00         ; mov [result], al
b8 01 00 00 00 ; mov eax, 1
31 db         ; xor ebx, ebx
cd 80         ; int 0x80

movmove的简写,也就是移动的意思,add则是加法的意思,此时我们就很容易看出来前面这段代码是在干嘛了。

将数字01移动到某个位置、然后对两个数值进行相加,并将结果存放在了某个位置。

这里的某个位置,实际上指的是一个叫做寄存器的东西,它是CPU用来临时存放数据的地方,能保存的数据量极小,但其速度极快,如果你未来对逆向工程感兴趣,那么你会大量看到、甚至用到它。

有多少机器码,就会有多少相应的汇编指令,它们之间是完全一一对应的。

因此在古老的时候(几十年前),编程便从原本的机器码编程转变为了汇编编程,这极大提高的程序的可阅读性、简化了编程过程。

一段汇编代码可能长下面这样:

section .data
    ; 定义数据
    num1 db 10
    num2 db 20
    result db 0

section .text
    global _start

_start:
    ; 加载 num1 到 AL 寄存器
    mov al, [num1]
    ; 加载 num2 到 BL 寄存器
    mov bl, [num2]
    ; 执行加法操作
    add al, bl
    ; 将结果存储到 result 中
    mov [result], al
    ; 退出程序
    mov eax, 1         ; 系统调用号 (sys_exit)
    xor ebx, ebx       ; 状态码 0
    int 0x80           ; 调用内核

虽然我们可以方便的阅读了,但由于计算机并不认识上面这些字符,它只认识0和1,所以仅仅这样肯定是不行的。

我们还需要一个叫做汇编器的东西,它的作用就是将上面这些我们能看懂、但机器看不懂的字符,转换为我们看不懂,但机器能看懂的机器码,并且转换后机器码所实现的功能与我们所写的汇编代码所实现的功能是完全一致的。

因为前面已经说了,汇编与机器码是一一对应的。

目前广泛使用的汇编器有以下这些:

  • nasm:支持x86与x86-64架构的CPU,一般都是英特尔,常用于Windows系统。
  • gas:GNU编译链所用的汇编器,常用于Linux、类unix系统
  • masm:微软自家的汇编器,主要用于Windows系统

我们并不需要过多的了解这些汇编器,因为只要你不深入底层逆向、或者做一些系统级别的代码优化,深入去研究它们对于你来说就是在浪费时间,在你日后的编程中也完全用不上。

你只需要知道存在着这么一个东西可以将汇编代码转换为其对应的机器码即可。

那么我们能不能看到机器码呢?

当然是能的,你只需要用十六进制编辑器随便打开一个windows系统上exe后缀可执行文件,就能看到其机器码,电脑执行exe程序的过程,实际上就是CPU在不断的执行exe程序内所包含的这些机器码。

1.2 C语言

虽然上面的汇编语言相比于机器码,已经实现了相当大的进步,但仍然是不够的。

因为对于一个新人来说,汇编语言的门槛仍旧是太高了,而且用其写程序,你需要关注的东西太多太多,稍不注意就是一个bug,并且不太好调试找到问题所在。

因此更进一步,就出现了C语言,它用相当简洁的语法使得新手上手编程也变得不是那么的困难了。

比如一个执行加法的C语言代码如下:

#include <stdio.h>
int main() {
    // 定义两个整数
    int num1 = 10;
    int num2 = 20;
    // 进行加法操作
    int result = num1 + num2;
    return 0;
}

如果你是纯新手,可能看起来这依旧有点复杂,但如果你将它与上面的汇编语言相比,我觉得你肯定会选择学习C语言,毕竟它简洁太多了!

而C语言的本质逻辑,其实和上面的过程比较类似,就是再套一层!

你只需要用C语言编写代码,然后通过编译器将C语言代码编译为汇编代码,再通过汇编器将汇编代码转换为机器码即可!

这里简化了其中的过程,实际上比这里描述的要复杂的多,因为C语言支持了非常多汇编语言本身不具备的功能,通过中间的转换过程编译器最终会将这些功能转换为等价的汇编代码、最后转换保存为机器码。

虽然其中的过程很复杂,但在我们实际的编程开发中并不需要考虑这么多,因为已经有前人将这些工具都集成在了一起。

因此大多数时候,我们只需要编写好C语言代码,然后执行一条命令,即可将其转换为对应的机器码。

我们学习C语言常能听到它的一个特性是跨平台,也就是我们只需要写一份C语言代码,就能在不同机器上编译运行,这是如何实现的?前面不是说不同CPU对于的汇编、机器码并不完全相同吗?

关键点就在于C语言代码已经脱离了具体的平台,因为它的代码里面已经没有某个平台指定的汇编了,那么只要给不同平台分别实现对应的编译器,将同一份C代码分别编译为不同平台的机器码,不就能解决这个问题了吗?

所以C语言跨平台,并不是指其编译后的程序可以在不同的平台上跑,仅仅指的是它同一份代码可以在不同平台编译。相比于汇编来说,这已经是相当大的进步了。

1.3 高级语言

虽然C语言已经拥有了大量令人惊喜的特性,但对于现代开发来说,仍然捉襟见肘。

因为现代程序员的用人成本很高,公司需要快速开发以节约成本。

而且对于大型项目来说(百万、千万行代码级别),使用C语言开发会导致难以维护,出现bug难以修复。

为了解决这一系列的问题,便更进一步,出现了更加现代化、高级化的语言。

其中C++便是引领一个时代的主力军,虽然和后面的语言相比它还算不上高级语言,但与C语言相比,它已经又实现了一次进化。

它引入了类、模板等相关概念,使得基于C语言的大型项目构建成为可能,因为C++是完全兼容C语言代码,你完全可以在C++中编写C语言代码。

甚至可以说,C++语言就是现代高级语言的鼻祖,因为几乎所有的现代、高级语言,都是在C++的基础之上进行阉割、增强的,你在各种高级语言中都能看到C++的影子。

虽然当下想要找一个C++相关的开发岗位难度较高,但我仍然认为新手有必要学习一下C++,它更像是一门内功,只要你将它吃透,日后无论你学什么语言,都会有一种似曾相似的感觉,学习效率飞快。

在C++之后的另一门引领一个时代的语言便是Java了,它无限增强了C++中类的概念,并将安全放在了第一位,在牺牲了效率的基础之上,换来了快速、跨平台开发等等一系列优点。

在2010-2020年左右,几乎就是一个由Java所引领的年代,各种网站、应用都使用Java进行开发。

直至Java语言开始收费之后,其便逐渐走了下坡路,即使如今Java的版本已经来到了jdk23,但时至今日用的人最多的还是古老的jdk8。

所以就目前来说,我并不推荐你去学java,因为它实在是太卷了、并且我十分不喜欢它那味同嚼蜡的语法。

作为Java的后继者,kotlin是一门不错的现代化语言,且开源免费,如果你对安卓系统软件开发感兴趣,那么可以尝试在了解了Java基础之后,从kotlin学起。

java之后,便是Python的时代了,时至今日,它仍然成为了大量小白入门的第一门编程语言,只因它足够的简单、且强大。

比如当下十分火热的AI大模型,便基于Python之上进行构建的。

但也千万别因为其火热便一头扎进去,因为其过于简洁的语法、糟糕的性能,导致其并不适合构建大型软件、网站。