计算机是如何计算1+1=2的?

原创石桥码农 2020-04-18 21:25:18

(第一部分编程基础)

一般我们谈论微信小游戏开发,指哪些内容呢?小游戏开发主要包括哪些内容呢?

主要指前端游戏界面的实现和交互逻辑的编写吗?显然不止这些,还应该包括支持前端数据存取的后端接口的实现。如果把概念扩展一下,还应该包括游戏关卡设计器的编写、角色人物设定器的编写、城池地图编辑器的编写、游戏管理后台的编写等等,这些工作都是为了「交付一个完全可运营的游戏」,为这个目标服务的,都属于小游戏的开发范畴。

当然对于初学者并不需要涉猎这么多,本书仅会从狭义上讲述小游戏开发,内容主要涉及前端界面的实现和后端接口的编写。初学者在学习这些内容时,一般会经历以下五个阶段:

了解编程基础,熟悉运行环境,学习使用开发工具,创建第一个项目

学习 js 语言,它是小游戏开发中最主要的编程语言

学习微信小游戏官方组件和平台提供的接口,这些决定了小游戏的能力边界

学习 Go 语言和云开发技术,它们都是为了编写后端接口程序

系统学习 js 语言和 Go 语言,建立完善的小游戏开发知识结构

这五个阶段分别对应五个部分。今天开始我们从第一部分编程基础开始。

(第一章 小游戏开发基础)

了解编程基础,主要了解编程语言的基本概念和电子计算机的运行原理。

计算机世界是程序员创造的一个虚拟的世界,在这个世界里,程序员就是上帝。昔日有「圣经」创世纪篇说,上帝创造了天地,上帝说「要有光」,于是就有了光。在计算机世界,程序员编编代码,回车一按,「运行」,字节码就永无停歇地跳动了起来了。

我们日常生活中使用手机订餐、看视频、刷微博,使用电脑打游戏、剪辑视频等,底层都是程序员编写的字节在跳动。这是一个建立在电磁基础上的虚拟世界,一旦没有了电,或者电磁理论发生了改变,这个虚拟的世界就不存在了。换句话说,我们使用的软件,及所有正在使用的数据也不存在了。

当然了电磁理论不会轻易改变,除非我们生存依赖的物质空间生存发生了改变,例如进到了另一个空间,当然这不属于本书讲述的内容,我们在此不必展开。对于初学者,我们只需要考虑一个问题:计算机是如何计算 1+1=2 的?

正文

这个问题看似简单,但通过它可以见微知著。所有表面上复杂的软件运行,都是底层简单的节字叠加。通过对这个问题的辨析,我们基本可以了解编程语言的基本概念和电子计算机的运行原理。

在了解这个问题之前,首先我们看一下,计算机的计算是如何达成的呢?

是通过编程语言实现的。那么,什么是编程语言呢?

像 C、C++、Java,C#、Python、Swift 这些都是编程语言,还有接下来在本书中我们要学习的 JavaScript、Go 也是编程语言,并且 Go 语言还可以说是现代高级编程语言之一。

编程语言的作用是什么?为什么人类不用自己的自然语言进行编程呢,这样不是就不用学习了吗?

人类语言是人与人之间沟通的桥梁,编程语言是人与计算机之间交流的工具。计算机听不懂人类的话语 —— 至少在目前这个时代是这样的,人类想控制计算机,就必须通过中间语言的翻译。

早期科学家通过拨动开头编程,后来通过纸带打孔编程,这些方式效率都极低下。后来程序员发明了汇编语言,但汇编晦涩难懂、表意不强,效率仍然不高。直到 1972 年,丹尼斯?里奇在贝尔电话实验室设计出了通用的 C 语言,编程才算彻底走出了蛮荒时代,步入了高速发展的现代。

自 C 语言之后,编程语言层出不穷,到目前为止全世界至少已经有 600 种编程语言。这些语言虽然复杂,但大多数并不是通用语言,只是为了解决某个特定领域的具体问题,所以并不值得大多数人深入学习;还有,编程语言种类虽然繁多,但像人类的自然语言语言一样,有其共通性,往往学习了一门语言,其它语言也容易触类旁通。

「弱水三千,只取一瓢饮」。现在我们仅以 JS——JavaScript 的简写,来探究计算机是如何计算 1+1=2 的?

首先,打开谷歌浏览器,右键单击空白处,选择 “检查”:

然后,打开浏览器自带的开发者工具,选择 Console 面板,如下所示:

除了通过右键菜单打开,在 Mac 上还可以通过快捷键 COMMAND+SHIFT+D 打开,效果等同。

现在在 Console 面板输入我们的算式:

1+1

按下回车键。

我们看到浏览器输出了 2:

这个过程看起来很快,但计算机内部其实已经经过 N 多复杂的运算操作。

纵观人类进化史,从学会使用石器到结绳记数,用了 100 万年。但自 1946 年第一台电子计算机 ENIAC 诞生,到 1990 年第一个浏览器 WorldWideWeb 诞生,仅用了区区 44 年,浏览器是怎么知道 1+1 等于 2 的?

问题探索到这里,为了使探索过程不致太过枯燥,请允许笔者将相关对象拟人化。笔者想,浏览器应该知道答案,因为这个结果是它告诉我们的。

于是笔者问谷歌浏览器,浏览器,请你告诉我,你是怎么知道 1+1 等于 2 的?

浏览器答一脸懵懂,我不知道啊,是 V8 告诉我的。

笔者再问道,V8 又是谁,这是台球馆里新的球名吗?

浏览器道,V8 是谷歌研发的 JavaScript 引擎,你发给我的 JS 代码,都是由它执行的。

于是笔者又去问 V8 引擎,听浏览器说,结果是你给我的。你是怎么知道 1+1 等于 2 的?人类世界上最聪明的孩子降生时,都不知道 1+1 等于 2 ,要知道人类进化今天已经用了 100 万年,你是怎么知道的?

V8 引擎双手一摊,尊敬的人类大人,我并不知道 1+1 等于几,我所有结果都是基于您的输入给出的。

笔者道,噢,是吗,那当浏览器把 1+1 发给你以后,你做了什么?

V8 引擎答题,我就是进行了常规的词法分析、语法分析,建立了语法树、符号表等...

浏览器见 V8 在掉书袋,打断道,这些都不要说了,这都是身为编译器 / 解释器的份内之事,大家都是这么干的,我解析 Html 标签也是这么干的。直接说你解析完了干了什么?

V8 引擎接着道,解析完了,我就使用了 MacroAssembler 库...

浏览器又打断道,MacroAssembler 库就是缩写为 masm 的 C++ 汇编库吧,我去年在老友 Strongtalk VM 那里见过他。咳咳,要知道 Strongtalk VM 可是大大鼎鼎的 Java 虚拟机 HotSpot JVM 的前辈呢!Java 可是比 JavaScript 快多了!

笔者想,浏览器果然是一个见多识广的人,要知道自 1990 年以来全世界的 www 网页都在它上面展示,怪不得它见多识广。

但笔者不喜欢浏览器的打断,这阻碍了我们探索问题。笔者道,浏览器别打岔,V8 你继续讲,使用 masm 干了什么?

v8 道,masm 提供了很多方法,可以理解为和 js 的语法能力是一一对应的。js 语句是什么,就调用对应的 masm 方法。例如 1+1 这句代码,对应调用 masm 的 C++ 代码是这样的:

#define __ masm.

__ mov eax 1

// 在这里__是一个宏

// 在预处理之后将被统一替换为 masm.

// 这一句是将寄存器 eax 设置为 1

__ add eax 1 // 这一句将寄存器的值加 1

__ ret eax // 这里返回寄存值的值

// 注:以上仅是伪代码示例

上面是 C++ 代码,在内存里生成机器码大概长这个样子:

#define __ masm.

此时 V8 貌似比浏览器见多识厂了。浏览器打断 V8 道,胡说!机器码都是像 010110010 这种二进制格式,你这种 B8、C0 是什么机器码?

V8 道,你整天只跟网页打交道,哪里知道计算机的底层原理。像 B8 01 00 00 00 这是二进制机器码的 16 进制表示,人类使用 16 进制阅读更方便,所以很多地方都以这种格式呈现。但我和 CPU 交流都是以 010110010 这种二进制方式进行的。

注:CPU 是 Central Processing Unit 的简称,中文即中央处理器。CPU 是一块包含数十亿电子元件的集成电路,是计算机的运算核心和控制核心。CPU 的功能主要是解释计算机指令以及处理计算机软件中的数据。CPU 主要包括算术逻辑运算单元、高速缓冲存储器及控制单元。

浏览器不说话了。此时 CPU 听到 V8 在叫他的名字,道,V8,那后面的「mov eax 1」是什么意思?我怎么从来没见你提起过?

V8 解释道:「mov eax 1」是机器码注释,是给人类大哥看的,我给你看的都是二进制字节码,是 010110010 这种的。像 mov 它只是诸如 1010 这种汇编指令的编程语言代名词。人类写的是 mov,汇编编译器译完就是 1010 了。

注:这也就是编程语言的作用,在人类和计算机中间充当一名翻译官。

V8 继续道,eax 是寄存器地址,「mov eax 1」这句指令就是将寄存器的值设为 1。同时,它下面那句代码 「add eax 1」是将寄存器的数值加 1。add 与 mov 不就是你的两个指令吗,CPU 大哥?如果我发错了指令,你是从来不理会我的。

CPU 点点头,嗯,表示同意。

笔者想,原来一句简单的代码,要经过词法解析、语法解析、转为汇编代码、汇编代码转为机器码等许多步骤,才能到达 CPU。

浏览器道道,好啊 V8,用户每天都骂网页慢,罪魁祸首原来在你这里。你将 JS 代码先转成汇编代码,再将汇编代码转成为机器码,一件事转好几道手续,这样能不慢吗?为什么你不直接转为二进制机器码呢?

浏览器貌似找到了一次反击的机会。

哈哈哈,v8 大笑道,浏览器,你只知其二,不知其二。JS 是解析型语言,如何直接编译成机器码?如果是这样,它不就和 Java 一样,是编译型语言了吗?

浏览器不服气,虽然是解释型语言,为什么不能先编译再执行?在 Java 版 JS 解释器 rhino 中,JS 脚本不是被编译为 Java 字节码执行的吗?

课时已经过去一半了,笔者感觉讨论主题有点偏离主题了,道,言归正传。V8,浏览器给你的 js 代码,你是读一行调用 masm 转化一行,还是读完了统一调用 masm 再转化的?

V8 道,是统一转化的,但这一切都是在内存那里折腾的。我有两个助手,一个叫初级全码编译器(学名叫 Full Code Generator),它将所有 JS 代码依次调用 masm 全部在内存中走了一遍;另一个叫优化能手编译器(学名叫 Crankshaft),它针对运行多次的代码,以初级全码编译器的编译结果为基础,再作一次优化编译,目的是使代码执行效率更高。

注:内存(Memory)也被称为内存储器,其作用是用于暂时存放 CPU 中的运算数据,以及与硬盘等外部存储器交换的数据。内存是由内存芯片、电路板、金手指等部分组成的。

一直默默无声的内存说话了,是啊,每次都把我折腾的晕头转向。别的 exe 执行文件,是先于我这里加载、后交给 CPU 运行,都是一次搞定的。唯有 V8 交给我的执行文件,连个名字都没有,代码忽长忽短,变化莫测。

注:exe 是 windows 系统下可执行文件的后缀名,英文全名是 executable file。

CPU 道,但我感觉 V8 交给我的机器码和普通 exe 执行文件交给我的机器码没有区别,在我这里它们都是合法公民。只要指令全部正确,我就能给出正确的运行结果。

笔者想,看来 V8 并不知道 1+1 为什么等于 2,它只是将浏览器交给它的代码在内存中编译一遍,然后交给 CPU 执行。V8 为了执行 JS 更快,大量占用了内存,是用「空间换时间」的方法,在计算机世界博得了「V8 引擎执行 JS 就是快」的美名。具体为什么 1+1 等于 2,还需要问问 CPU。

于是笔者对 CPU 道,CPU,你说只要指令正确,你就能返回正确结果。那么 V8 将 1+1 的机器码传给你,你都做了什么?

CPU 道,报告人类大人,我什么都没有做。我做的一切,都是让按照您的指令完成的,这一切都是您的智慧啊!

CPU 态度很诚恳,并不像在拍人类的马屁。只有一种解释,那就是 CPU 是人类科学家设计的。

CPU 继续道,首先,当我看到「mov eax 1」这条指令,就知道这是叫我将值 1 移动到寄存器 eax 处。

内存问道,你是怎么什么调度指令的?怎么知道什么指令应该怎么执行的呢?

CPU 答道,我有一个助理,叫指令指挥官,他负责指令的分类与调度。例如,他看到指令是「010100010010」,首先从前 4 位 0101 判断,这是一个寄存器设置命令,于是就打电话通知寄存器老头来领取数据包裹;如果看到前 4 位是 1010,就知道这是一个加法指令,就打电话通知算术运算单位的加法器来领取任务。待加法器计算完了,他又会将运算结果发给寄存器老头保存。

这时浏览器貌似对 CPU 的工作原理也起了好奇之心,说道,不要说人话,讲机器语言。寄存器、算术运算单元都是你的单位员工,指令指挥官是如何给你的单位职员分派任务的?他看到 0101,是怎么知道应该分派给寄存器老头的?

CPU 道,我用拟人化的指令指挥官类比,是为了方便大家理解。从计算机角度讲,比如 0101 这 4 个 bit,依次代表 4 个路口,每个路口有两个岔,0 向左转,1 向右转,这样 0101 一路走下来不就知道是哪个职员负责了。

笔者想,指令分派确实简单,问题关键还在加法器上,1+1 等于几是它算出来的。

浏览器问道,CPU,那加法器是如何计算 1+1 的呢?

CPU 道,这就没那么简单了。加法器并不知道 1+1 等于几,加法器是由半加器累加组成的,而半加器是由一个异或门加一个与门组成的。如下所示是一个半加器:

注:在上图中,A、B 是输入,S 是结果,C 是进位结果。

其中异或门的逻辑是这样的:

__ mov eax 1

如果说异或门电路有点复杂,那么可以拆开看异或门。一个异或门可以由四个与非门表示。如下所示是四个非门:

综上,加法器是由半加器组成的,半加器是由异或门和与非门组成的,异或门又可以由与非门组成,所以,整个加法器都可以看作是由与非门组成的。(注:事实上,任何一个算术逻辑都可单独由「与非逻辑」或「或非逻辑」来实现。)而一个与非门简单电路的物理设计是这样的:

如上所示,它是由开关设计实现的。x、y 是两个开关,其开状态为 1,关状态为 0。x、y 相当于与非门中的 A、B。x、y 状态全开,以及任何一个状态为开,电路都是不通的。只有当 x、y 状态全为关,电路才是通的。

既然与非门可以由开关设计组成,异或门同样也可以。异或门加一个与非门组成了半加器,多个半加器累加到一起,组成了全加器,如下所示:

低位半加器的进位结果恰是高位半加器的输入,合在一起就组成了一个多位全加器。所以,我的算术运算单元的运算能力也不是无限的,能算多大数字是由硬件决定的。

笔者道,这下明白了,CPU 并不知道 1+1 等于 2,之所以 1+1 能算出等于 2,是人类在设计 CPU 的时候赋能给它的。而 CPU 内所有逻辑的运算,归根结底又都是开关的开合。从这点来看,计算机的鼻祖竟然是小小的开关。

浏览器问道,CPU,你的加法器是由众多开关实现的。那减法运算、乘法运算、除法运算又是怎么实现的?

CPU 道,减法在我这里也是加法,乘法是换算为多位加法累加的,除法又可以换算为乘法。所有四则运算都可由加法变换实现。包括文字、音频和视频信息的处理,在我这里都是二进制的加减乘法与逻辑与非。

浏览器又问,那这样说,在你内部肯家有很多很多的开关喽?

CPU 说,是的,人类发明了一种双极型三极管,简称晶体管。如下所示:

每个晶体管相当于一个电路开关。我是 2010 年出厂的酷睿 i7-980X,晶体管数量不多,也就 11 亿多个吧。

注:现代计算机 CPU 里用的是更高级更微小的场效应管,本质上也是一种晶体管。晶体管的发明是 20 世纪中叶科学技术领域有划时代意义的一件大事。1956 年诺贝尔物理学奖授予美国加利福尼亚州景山贝克曼仪器公司半导体实验室的肖克利、美国伊利诺斯州乌尔班那伊利诺斯大学的巴丁和美国纽约州谬勒海尔贝尔电话实验室的布拉坦,以表彰他们对半导体的研究和晶体管效应的发现。

笔者不禁感喟,原来我在浏览器里简单敲一个简单的 1+1,CPU 内部就要做那么多事情。

计算机并没有智能。我们从宏观上看,计算机仿佛拥有了智能一般,其实都是通过数以亿计的场效应晶体管开关电路实现的,并且这种能力也都是人类赋予它的。

在人的大脑中,也有几十亿个神经元,像一个计算机一样。人为什么拥有智能?或者人根本也并不拥有智能,在上帝那里,我们的大脑也只是按照他老人家的设计表现的开关状态而已。

通过这节课的学习,我们了解了基本编程概念,明白了电子计算机的基本运行原理,知道代码在计算机内部是如何运行的了。下一课,我们将着手了解微信小游戏的运行环境和开发环境,并亲自创建第一个可以运行的小游戏项目。

2019 年 5 月 6 日

真正的勇士,是看清生活的真相后,依然热爱生活。

书山有路勤为径,艺海无涯苦作舟。

说什么真理无穷,进一寸有一寸的欢喜。

不能穷尽所有,但要尽己所能。

这篇推文是「小游戏从0到1」书稿的一部分。

本页共117段,7946个字符,19465 Byte(字节)