文章

【信奥业余科普】05:人类怎么跟计算机说话?编程语言和操作系统的故事

第五篇信奥基础知识科普:在前一篇我们见证了恐怖硬件底座(超大规模集成电路)的竣工后,本篇将讲述软件工程师如何用语言“教导”它。从枯燥的打孔纸带到优雅的 C++,带你了解人类是如何跨越底层机器指令的巨大鸿沟,并创造出在底层统筹一切的“操作系统大管家”。

写在前面的话:这是一系列专为对信奥(信息学奥赛)感兴趣的中小学生及家长朋友们准备的业余科普文章。笔者并非计算机历史学专家,受自身学识所限,文中若存在不严谨或考证疏漏之处,还望各位读者海涵并指正。

推出本系列的初衷主要有三点:

  1. 拓宽视野:在动手敲代码之前,全面了解计算机软硬件的发展脉络。
  2. 激发兴趣:通过深入浅出地讲述前沿技术与历史故事,希望能点燃中小学生对计算机科学的好奇心。
  3. 课余读物:哪怕只是作为打发闲暇时光的休闲阅读,也能让大家在轻松的氛围中收获知识。

本系列文章往期回顾:

在计算机科学的早期,人类面临着一道难以逾越的“鸿沟”:机器只懂枯燥的 0 和 1(机器语言),而人类习惯于用自然语言和数学公式(人类语言)去思考问题。

为了填平这道巨大的鸿沟,让普通人也能自如地指挥计算机,前人进行了一场长达数十年的伟大技术攀登。今天,就让我们看一看,同学们用来写信奥考题的 C++ 语言,究竟是如何在这个过程中诞生的。

在上一篇文章中,我们见证了计算机硬件从“房屋般大小的电子管”到“指甲盖大小的集成电路”的疯狂微缩奇迹。但无论硬件算力变得多么恐怖,它的底层逻辑仍然没有改变——它只能读懂 0 和 1。

为了搞明白为什么我们需要学习 C++,让我们先倒带回第一代电子管时代,看看第一代程序员是在经历一种怎样的折磨。

一、 噩梦般的起点:机器语言与打孔纸带

在冯·诺依曼刚刚提出“存储程序”架构时,计算机的运行方式正迎来质的飞跃。

我们需要先理清一个关键的疑问:冯·诺依曼之前和之后,计算机的本质区别是什么?

  • 之前(硬连线或机械读取):要想改变计算机的任务(比如大名鼎鼎的 ENIAC),需要几十个工程师如同电话接线员一般,手工插拔成千上万根物理线缆,相当于用“改变硬件线路”来编程。或者像更早期的机器,纸带必须时刻挂在机器上,机械齿轮把纸带往前转一格,机器才跟着执行一步指令,算得极其缓慢。
  • 之后(存储程序):程序指令被视为一种“数据”。它不再需要改变硬件接线,而是直接全部存进电子化的“内存”里,让 CPU 以接近光速的电子频率去抓取执行。

虽然机器的执行速度虽然快了,但对于第一代程序员来说,编写和输入程序的体验依然犹如噩梦。 因为他们只能直接用 机器语言(Machine Language) 也就是纯正的 0 和 1 序列来写代码。想做一次加法,要在图纸上写下类似 10110000 01100001 这样的天书,然后再人工去纸带上“抠”出对应的小孔。一旦手抖打错一个孔,整段程序直接奔溃,几天的心血瞬间灰飞烟灭。那个时候的程序员,真可谓是眼力极佳、手极稳的“高级纺织工”。

二、 第一次抽象:汇编语言(Assembly Language)

聪明的工程师们很快就受不了这种非人的折磨了。他们想:“既然机器这么擅长处理对应关系,我们为什么不让机器自己来帮我们翻译呢?”

于是,就在 20 世纪 50 年代初,他们发明了一种稍微“人道”一点点的语言——汇编语言。 他们用一些极其简短的英文缩写(助记符)来代替长长的 0 和 1 指令串。比如:

  • ADD 代表加法运算的 01 代码;
  • MOV 代表把数据搬运到某个地方;
  • JMP 代表跳到下一行执行。

写完这种带有英文缩写风格的汇编代码后,再用一个叫做 “汇编器(Assembler)” 的程序,把这些英文缩写自动“翻译”回机器认识的 0 和 1。

💡 衍生思考:我们今天学 C++,为什么还要了解古老的汇编语言?

  1. 应试考点必考:在 GESP 和 CSP 的初赛基础知识中,经常要求考生区分三大语言。必须牢记:汇编语言属于低级语言,它最大的特点是使用英文缩写(助记符);且它不能被计算机直接执行,必须经过“汇编器”翻译。
  2. 体会抽象的伟大:虽然汇编语言依然极其底层(需要了解 CPU 内部有多少个寄存器、内存怎么排布),但在当时,这是人类试图用自然语言驯服机器的伟大第一步!只有了解了这种带着镣铐跳舞的痛苦,未来在写 C++ 时,你才会真正感恩高级语言带来的自由。

三、 跨越鸿沟:高级语言(High-level Language)的大爆发

然而,汇编语言依然太难用了。不同的计算机硬件,其汇编语言是不一样的。这就意味着,你在 A 牌子的电脑上辛辛苦苦写了几个月的程序,换到 B 牌子电脑上,完全无法运行,必须从头再学一种新的汇编语言重新写一遍。

🔍 深度探究:为什么硬件不同,汇编就不通?

本质原因是:汇编语言是 CPU 硬件指令集的“一一对应”。 不同厂商设计的 CPU(比如英特尔的 x86 和苹果/华为手机用的 ARM),它们内部的电路构造、寄存器(存放临时数据的抽屉)的数量和名称完全不同。写汇编时,程序员必须精确指明数据搬运到哪个具体的寄存器里。这就像给不同设计的机器人下指令:你对“人形机器人”说“迈左脚”,那针对“履带机器人”就得改成“左侧转轮前进”,两者根本无法直接复用。

为了彻底解放程序员的大脑,以 葛丽丝·霍普(Grace Hopper) 为代表的先驱们提出了一个极其宏大的愿景:我们应该创造一种能让人类像写英语作文、列数学算式一样舒服的编程语言!无论底层的硬件是什么牌子,代码只要写一次,就能到处运行。

这就是 高级语言(High-level Language) 的雏形。而这个愿景第一个真正落地的硕果,是 1957 年由 IBM 公司的约翰·巴克斯(John Backus)领导团队开发的 FORTRAN(取自 Formula Translation,公式翻译)。它是世界上第一个被广泛采用的高级编程语言。

在 FORTRAN 诞生之前,业界普遍认为:“程序员不写机器码,那是偷懒;而且机器怎么可能看懂人类写的公式呢?”巴克斯顶着巨大的压力证明了,只要有一个聪明的“编译器”,人类真的可以摆脱 0 和 1 的苦海,直接用数学公式写代码,且运行速度并不比手写机器码慢多少。这次革命直接宣告了“手动抠孔”时代的终结。

自 20 世纪 50 年代中后期开始,高级语言迎来了寒武纪般的大爆发:

  1. Fortran(1957年):专为科学计算和复杂数学公式量身定做。
  2. C语言(1972年):极其强大而优雅,它兼具了高级语言的易读性和汇编语言对硬件的强大掌控力,成为了后来无数现代操作系统的基石。
  3. C++(1983年左右诞生的超集):也就是今天各位信奥同学们苦学的“主角”。它在 C 语言的基础上加入了面向对象(把代码抽象成世界上的一个个实体对象)的概念,使其极其适合开发大规模的复杂软件体系结构结构。

那么,机器又是怎么看懂 C++ 的呢? 同样是靠一个超级复杂的“翻译官”——编译器(Compiler)。你敲下 if...elsefor 循环后,编译器会在极短的时间内进行极为复杂的语法分析,最终把这篇高级语言“长篇小说”,严丝合缝地翻译成了那台特定电脑能看懂的,长达数万行的 0 和 1 机器码(通常被打包成 .exe 可执行文件)。

考试相关:在信奥(如 CSP 初赛)中,经常会考察这几种语言的区别。牢记:机器语言是唯一能被计算机直接执行的语言;汇编语言是低级语言;而 C、C++、Python 等都属于高级语言,必须经过“编译”或“解释”才能被机器执行。

四、 后勤大管家:操作系统(Operating System)

前面三节,我们沿着“编程语言”这条线,走完了从机器码到汇编再到高级语言的进化之路。但千万不要因此产生一个错觉:以为是先有了全部语言,然后才有了操作系统。事实上,操作系统的诞生历史,几乎和编程语言的进化是同步交织、齐头并进的。 让我们先把时间线拉回去,补上这条平行发展的另一半故事。

早在 20 世纪 50 年代,计算机还在使用“打孔纸带”的远古时期,第一代极其简陋的“管家”就已经出现了——比如 GM-NAA I/O。它是一个“批处理系统”:程序员把一摞纸带交给机房管理员,管理员攒成一批喂给电脑,电脑不停歇地一个接一个跑完,程序员再来取结果。它虽然原始,但已经体现了操作系统最核心的使命:替人管理硬件,让人不必事事亲力亲为。 那时候,这些系统全部是用汇编语言甚至机器码写成的。

到了 60 年代末,一个名为 Multics 的雄心勃勃的系统诞生了,它试图一步到位造出一个功能齐全的超级操作系统。但由于它太庞大太复杂了,最终难以为继。

贝尔实验室的两位大神——肯·汤普森(Ken Thompson)丹尼斯·里奇(Dennis Ritchie)——决定“另起炉灶”。他们先用汇编语言在一台破旧的机器上写出了一个极简版的系统,取了个谐音自嘲的名字叫 Unix(意为“只做一件事的简版 Multics”)。然而,他们很快发现用汇编编写和维护一整个操作系统简直是折磨。于是,丹尼斯·里奇发明了 C 语言,并用 C 语言把 Unix 从头到尾重写了一遍。

🔍 深度探究:C 语言凭什么能替代汇编来“造系统”?

它的秘诀在于两个核心优势:

  1. “抽象”的力量:在汇编语言里,你必须操心“要把数据放进第几号寄存器”;而在 C 语言里,你只需要给数据起个名字(变量),剩下的琐事交给编译器。它把复杂的硬件指令,转变成了符合人类思维的逻辑表达。
  2. “跨平台”的可能:只要不同机器上装有对应的 C 语言编译器,你的代码逻辑就几乎不需要修改。编译器会像一个精通各国语言的翻译官,自动把你的 C 代码翻译成最适合那台硬件的“方言”。Unix 因此得以快速移植到各种不同的计算机上。

这段历史告诉我们一个关键事实:C 语言和 Unix 操作系统,是互相成就的一对搭档。 C 语言为了造操作系统而生,而操作系统也正是有了 C 语言才得以走向成熟和普及。

那么,操作系统到底在帮我们做什么?

如果没有操作系统,程序员就得进行 “裸机开发(Bare Metal)”。你的代码里不能直接写 cout,因为没有管家帮你联系显示器——你得自己写代码直接给显卡发送原始的电压信号,去一个个点亮屏幕上的像素点。随着计算机越来越发达,一台电脑里装上了硬盘、显示器、声卡、网卡,同时还要一边听歌、一边打字、一边在后台下载游戏,谁来协调这一切?

为了把程序员从硬件管理的泥潭中拯救出来,操作系统应运而生,承担起了 “超级大管家” 的角色。

像大家最熟悉的 Windows、苹果电脑用的 macOS、手机用的 iOSAndroid,以及服务器领域雄霸天下的 Linux,它们全都是操作系统。

我们可以把操作系统想象成一个尽职尽责的 “超级大管家”

  1. 向下管理硬件设备:它替你把键盘、鼠标、屏幕、硬盘等各种杂乱无章的硬件管理好,封装成极其好用的接口。
  2. 向上服务应用程序:当你写的 C++ 程序想要在屏幕上输出一行字(比如 cout << "Hello World";)时,你的程序其实不是直接去控制显示器,而是转身向大管家喊了一嗓子:“管家,帮我在屏幕上画几个字!”大管家收到命令,转身去控制了显示卡。
  3. 公平分配资源:你有好几个程序同时在跑,大管家会极其公平且快速地切换着把 CPU 的算力分给它们,让你产生“它们在同时运行”的错觉。

小结

回顾一下这篇文章的脉络:

  1. 硅片上的晶体管只认识 0 和 1,所以最早的程序员只能用机器语言直接写二进制指令。
  2. 为了少写点 0 和 1,人们发明了汇编语言,用英文缩写代替二进制,但它仍然和硬件绑死。
  3. 高级语言(如 Fortran、C、C++)的出现,让程序员可以用接近人类思维的方式写代码,由编译器负责翻译成机器码。
  4. 与此同时,操作系统也在不断进化——从最早的批处理系统,到 Unix,再到今天的 Windows、macOS、Linux。它帮我们管好了所有硬件,让我们写程序时不必操心底层细节。

这四样东西层层叠加,构成了我们今天使用计算机的基本框架。


下期预告

到目前为止,我们聊的都是一台计算机自己怎么工作。但现实中,计算机早已不是各自孤立的——你打游戏时能遇到全国各地的队友,发一条消息几秒钟就能到达太平洋对岸。这背后是怎么实现的?

下一期,我们就来聊聊:计算机之间是如何联网通信的?从最早的军用实验网络,到今天无处不在的互联网,这中间经历了什么。


所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

"luogu-"系列题目可在洛谷题库进行在线评测。

"bcqm-"系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权