第1章 什么是编译原理?电脑的翻译小秘密 / Chapter 1 What is Compiler Principle? The Computer's Little Translation Secret
周末的社区国际儿童联欢会现场,你攥着刚抄好的《小星星》中文歌词,站在一群金发碧眼、黑头发黄皮肤的小朋友中间,有点发愁:来自美国的Mike只会说英文,日本的佐藤只会说日语,他们都看不懂你写的“一闪一闪亮晶晶,满天都是小星星”,更别说跟着你唱了。你挠挠头,突然想起妈妈教过你翻译,不如你来当一次“翻译官”,把中文儿歌翻译成他们都能懂的语言,还要保证唱的时候调子顺、意思对、押韵也顺口?
你有没有觉得,这个场景和电脑工作的时候特别像?我们平时用的手机、平板、电脑,其实就像那个只会听“特定语言”的小朋友——它们只能听懂由0和1组成的机器语言(Machine Language),比如01000001代表字母A,01100001代表字母a,一堆0和1堆在一起,人类看了头都大,更别说让电脑执行我们写的程序了。而我们平时用的Python、Scratch、C++这些编程语言(Programming Language),就像中文儿歌、英文儿歌,是人类能看懂的“友好语言”。这时候就需要一个专门的“翻译官”,把人类写的编程语言,一丝不苟地翻译成电脑能听懂的机器语言,这个“翻译官”就是编译器(Compiler),而它干活用到的所有方法和规则,就是我们今天要讲的“编译原理”。
你当翻译官翻译《小星星》的时候,肯定不会一上来就整首翻,得一步步来:首先你得先把中文歌词里的每个字都认清楚,知道“闪”是动词,“亮晶晶”是形容词,“星星”是名词,不会把“闪”看成“肉”,不会把“星星”看成“猩猩”;然后你得看看句子的结构是不是对的,“一闪一闪亮晶晶”是“一+数量词+动词+形容词”的结构,不能翻译成“一闪亮晶晶一闪”,不然语序乱了,唱的时候也不顺口;接着你得保证翻译出来的意思和原来的一样,不能把“小星星”翻译成“大太阳”,也不能把“满天都是小星星”翻译成“地上都是小石头”,不然意思全错了;最后你还要调整一下翻译的版本,让它符合英文儿歌的押韵习惯,唱起来顺口,Mike和佐藤拿到就能直接唱。
你看,翻译儿歌的四步,和编译器翻译代码的过程几乎一模一样。编译器的工作也可以分成四个核心步骤,每一个步骤都有对应的“魔法规则”:
1. 第一步:词法分析——先认清楚每个“字”
翻译儿歌的时候,你第一步要做的就是把每个字都认对,不会把“闪”看成“肉”,编译器也一样,它翻译代码的第一步叫词法分析(Lexical Analysis),核心任务就是把代码里的所有符号、单词一个个“揪出来”,然后认出它们到底是什么。
比如你写了一段简单的Python代码: ```python
这是你要翻译的“中文儿歌”
print("Hello, 编译小魔法师!") ``` 编译器做词法分析的时候,会从左到右一个个看这些符号:第一个看到`print`,认出它是“关键字”(就是编程语言里自带的有特殊意义的词,相当于中文里的“动词”);然后看到`(`,认出它是“左括号”,属于标点符号;接着看到`"Hello, 编译小魔法师!"`,认出它是“字符串”,就是你要打印出来的内容;最后看到`)`,认出它是“右括号”。整个过程就像你认字的时候,一个个把“一”“闪”“亮”“晶”“晶”认出来,知道每个字的读音和意思,一个都不能认错。
如果词法分析的时候发现了一个不认识的符号,比如你写代码的时候不小心打了一个`@`,而Python里没有这个符号的用法,编译器就会直接告诉你“第X行有个不认识的字”,就像你翻译儿歌的时候看到个生僻字,会先停下来查字典,不会瞎翻译。
::: tip 小知识 你知道吗?词法分析的名字来自“词素”(Morpheme),就是语言里最小的有意义的单位,比如中文里的“闪”是一个词素,“亮晶晶”是三个词素,编译器词法分析就是先把代码拆成一个个最小的有意义的“词素”,然后再认出它们是什么。:::
2. 第二步:语法分析——看看句子结构通不通
认完所有字之后,你要看看句子的结构是不是对的,比如“一闪一闪亮晶晶”是“数量词+动词+形容词”的结构,你不能翻译成“一闪亮晶晶一闪”,不然语序乱了,唱的时候也不顺口。编译器的第二步叫语法分析(Syntax Analysis),核心任务就是把词法分析认出来的所有单词,按照编程语言的语法规则排好队,看看结构是不是正确的。
比如你刚才写的`print("Hello, 编译小魔法师!")`,语法分析会检查:`print`是关键字,后面是不是跟了括号?括号里是不是有字符串?如果写成`print "Hello"`(少了括号),或者`Hello print("")`(语序错了),语法分析就会发现结构错了,直接告诉你“语法错误,请检查括号和语序”。
这一步就像小学语文老师改作文,会看你的句子是不是通顺,有没有语病,比如“我吃了苹果”是对的,“苹果吃了我是”就是语序错了,老师会给你打叉。编译器的语法分析就是那个“改作文的老师”,专门检查代码的“句子”有没有写错。
我们可以用下面的流程图,看看编译器的四步工作过程: ```mermaid graph LR A[源代码<br/>(比如你写的Python代码)] --> B[词法分析<br/>(认出每个符号/单词是什么)] B --> C[语法分析<br/>(检查句子结构是否正确)] C --> D[语义分析<br/>(检查代码意思是否正确)] D --> E[中间代码生成与优化<br/>(转换成通用语言,调整得更高效)] E --> F[目标代码生成<br/>(转换成0和1的机器语言)] F --> G[电脑执行<br/>(运行程序,得到结果)] ```
3. 第三步:语义分析——保证意思不翻错
句子结构对了之后,你还要保证翻译出来的意思和原来的一样,不能把“小星星”翻译成“大太阳”,也不能把“满天都是小星星”翻译成“地上都是小石头”,不然意思全错了。编译器的第三步叫语义分析(Semantic Analysis),核心任务就是检查代码的“意思”是不是正确的,有没有逻辑错误。
比如你写了一段代码: ```python a = 5 b = "小星星" print(a + b) ``` 词法分析和语法分析都会觉得这段代码没问题:`a`是变量,赋值为数字5,`b`是变量,赋值为字符串“小星星”,`print`后面跟了括号和变量,结构完全正确。但是语义分析会发现:数字和字符串是不能相加的,就像你不能说“5个小星星”,语义分析就会直接告诉你“类型错误,不能把数字和字符串相加”。
这一步就像你翻译儿歌的时候,虽然“一闪一闪亮晶晶”结构是对的,但是如果你翻译成“一闪一闪亮太阳”,意思就错了,语义分析就是那个专门检查“意思有没有翻错”的老师,保证翻译出来的内容和原来的完全一致。
4. 第四步:目标代码生成与优化——让翻译更顺口、更高效
意思对了之后,你就可以把中文儿歌翻译成英文了,比如“一闪一闪亮晶晶”翻译成“Twinkle, twinkle, little star”,还要调整一下,让它符合英文儿歌的押韵习惯,唱起来顺口。编译器的第四步就是目标代码生成(Target Code Generation)和优化(Optimization),核心任务是把前面检查好的代码,转换成电脑能直接执行的机器语言,还要调整得让电脑运行的时候更快、更省电。
比如你写的Python代码,经过前面三步检查没问题之后,编译器会先把它转换成一种中间的“通用代码”,然后优化一下,比如把重复的计算合并,把没用的代码删掉,最后转换成电脑能听懂的0和1的机器语言,电脑拿到之后直接就能运行,不用再做别的处理。
这一步就像你翻译完儿歌之后,调整一下语序,让它更押韵,小朋友唱起来不拗口,编译器的优化就是让代码运行得更快,比如你玩《王者荣耀》的时候,编译器的优化会让游戏加载更快,玩起来更流畅,不会卡顿。
我们可以用下面的角色图,看看翻译官(编译器)在整个过程中扮演的角色: ```plantuml @startuml actor 人类/程序员 as human actor 电脑 as computer actor 编译器/翻译官 as translator
human -> translator: 给出编程语言/中文儿歌 translator -> translator: 词法分析/认字 translator -> translator: 语法分析/分析句子结构 translator -> translator: 语义分析/检查意思 translator -> translator: 优化/调整得更顺口 translator -> computer: 给出机器语言/翻译好的英文儿歌 computer -> computer: 执行程序/唱儿歌 @enduml ```
---
你可能会问,除了翻译编程语言,我们身边还有没有别的“翻译官”?当然有,而且多到你每天都能碰到:
- 你钢琴课上用的五线谱,就是音乐的“编程语言”,钢琴家就是翻译官,把纸上的符号翻译成你能听到的音乐;
- 视障小朋友用的盲文,就是把汉字翻译成凸点的“语言”,他们的手指就是翻译官,一摸就知道写的是什么;
- 你玩无线电对讲机的时候,摩尔斯电码把文字翻译成“滴-滴-答-滴”的短长信号,报务员就是翻译官,把信号翻译成文字;
- 甚至你妈妈做饭的时候,把菜谱上的“少许盐”翻译成“倒3克盐”,菜谱就是“语言”,妈妈就是翻译官。
所有的翻译官,核心工作都是把“一方能懂的内容”,转换成“另一方能懂的内容”,编译器就是专门翻译编程语言的翻译官而已。
很多小朋友觉得,编译原理是程序员才需要学的东西,和我没关系?其实你每天玩的游戏、看的动画片、用的学习APP,背后都有编译原理的影子:你玩的《我的世界》《原神》,是程序员用C++写的代码,经过编译器翻译成电脑能懂的语言,你才能在手机上玩;你用的微信、抖音,是程序员用Java、Go写的代码,经过编译器翻译,你才能刷视频、发消息;甚至你玩的Scratch编程,你拖拽的那些彩色模块,也是经过Scratch自带的编译器翻译成机器语言,机器人或者动画才能按照你的想法动起来。
甚至你玩的《我的世界》里的红石电路,也有类似编译原理的过程:你放的红石火把、中继器、活塞,就是红石“编译器”要翻译的“代码”,你拉下拉杆的操作就是“输入指令”,红石电路会把这些指令翻译成红石信号的通断,让活塞门自动打开,让自动农场自动收割小麦,让音乐盒自动播放你写的曲子,本质上就是编译器把人类的“操作指令”翻译成了电路能听懂的“信号语言”。
::: tip 小挑战 身边的翻译官大搜查:快去找一找你身边的3个翻译官例子,说说它们分别翻译什么内容?比如: 1. 爸爸把英文的产品说明书翻译成中文给你看,这里的翻译官是爸爸,源语言是英文,目标语言是中文; 2. 老师把《静夜思》翻译成英文讲给外国小朋友听,这里的翻译官是老师,源语言是中文古诗,目标语言是英文; 3. 微信的“翻译消息”功能,把外国朋友发的英文翻译成中文,这里的翻译官是微信的翻译程序,源语言是英文,目标语言是中文。 :::
你知道吗?世界上第一个编译器是1952年由美国科学家格蕾丝·霍珀(Grace Hopper)发明的,她当时给编译器起名叫“Compiler”,来自英文“compile”,意思是“编辑、编纂”,因为她觉得编译器就像编辑一样,把程序员写的零散的代码编辑成电脑能懂的整体程序。格蕾丝奶奶还发明了我们现在用的编程语言COBOL,被大家称为“计算机之母”。当年她提出要做编译器的时候,所有人都觉得她疯了,说“电脑只会算数,怎么可能翻译语言?”结果她真的做出来了,把原来需要几周才能写完的代码,缩短到几小时就能写完,大大提高了编程的效率,就像当年大家都觉得翻译官只能翻译人说的话,没想到后来还能翻译电脑的“话”。
很多小朋友会有个误区:觉得编译器就是把代码翻译成0和1的东西,其实不是哦,编译的过程里还有很多“魔法步骤”,后面我们会慢慢讲:比如“词法分析”就像翻译的时候认字,“语法分析”就像分析句子结构,“抽象语法树”就像翻译的时候列的大纲,把句子的结构理清楚,才不会翻错;“代码优化”就像你写完作文之后改错
错别字、不通顺的地方,改完之后读起来就顺口多了。编译器的优化步骤也是一样的道理:不仅要把代码翻译对,还要让电脑运行的时候更快、更省电、占的空间更小。比如你手机里玩的《原神》,安装包只有10GB左右,要是没有编译器的优化,可能要几十GB,你的手机根本装不下;你打开游戏的时候,优化好的代码只需要几秒钟就能加载完,要是没有优化,可能要等十几分钟,你早就等不及了。
有的小朋友可能会问:编译器是把整段代码一次性翻译完吗?有没有那种“翻译一句、跑一句”的翻译官?当然有!我们平时说的解释器(Interpreter),就是“实时翻译官”。我们可以把编译器和解释器比作两种不同的翻译方式: ```mermaid graph LR subgraph 编译器:全文翻译官 A[整首中文儿歌] --> B[一次性全部翻译成英文] --> C[直接唱完整的英文儿歌] note right of B: 翻译一次,运行多次,速度快 end subgraph 解释器:实时翻译官 D[唱第一句中文] --> E[翻译成第一句英文] --> F[唱第一句英文] D1[唱第二句中文] --> E1[翻译成第二句英文] --> F1[唱第二句英文] note right of E: 翻译一句,运行一句,改歌词方便 end ``` 比如你平时玩Scratch编程的时候,拖拽完一个“移动10步”的模块,动画立刻就会动,这就是解释器在实时翻译你写的模块指令,所以改起来特别方便;而玩《王者荣耀》的时候,需要等几秒钟加载完才能玩,就是因为游戏代码已经被编译器一次性翻译好了,所以运行速度特别快。
你可能会觉得:“我又不写代码,学编译原理有什么用?” 其实编译原理早就藏在你的生活里了:
- 你玩《我的世界》的时候,搭的红石自动农场:你拉下拉杆的指令就是“源代码”,红石电路就是“编译器”,它会把这些指令翻译成红石信号的通断,让活塞推动水流浇小麦、让漏斗收集掉落的麦子、让发射器把麦子弹进箱子里,整个过程就是一次完整的“编译-执行”过程;
- 你用微信给外国朋友发消息的时候,微信的翻译功能会把你的中文翻译成英文,把对方的英文翻译成中文,这个翻译功能背后也是编译原理的规则:先把句子拆成单词,再分析语法结构,再调整意思符合语境,最后翻译成目标语言;
- 甚至你妈妈用空气炸锅做蛋挞的时候,你按“蛋挞模式”的按钮,空气炸锅的控制系统就是“编译器”,把你按按钮的指令翻译成“180度加热15分钟”的机器指令,加热管就会开始工作,做出香喷喷的蛋挞。
> 💡 小知识 / Little Knowledge > 现在全球已经有超过900种编程语言,每一种语言都有自己的编译器或者解释器,就像地球上有几千种人类语言,每一种语言都需要对应的翻译官。根据TIOBE编程语言排行榜的数据,最受欢迎的10种编程语言(比如Python、C++、Java)每天会被编译器翻译超过100亿次,相当于每个地球人每天都要“说”10次编程语言。 > > There are now more than 900 programming languages in the world, and each language has its own compiler or interpreter, just as there are thousands of human languages on earth, and each language needs a corresponding translator. According to TIOBE programming language ranking data, the 10 most popular programming languages (such as Python, C++, Java) are translated by compilers more than 10 billion times a day, which is equivalent to each person on earth "speaking" programming languages 10 times a day.
编译原理的“祖师奶奶”:格蕾丝·霍珀的故事
我们之前提到世界上第一个编译器是格蕾丝·霍珀(Grace Hopper)奶奶发明的,她的故事特别有意思,就像魔法故事里的女巫发明了第一本魔法书。
格蕾丝奶奶1906年出生在美国,她小时候就特别喜欢拆东西:家里的钟表坏了,她就拆开来研究里面的齿轮怎么转;妈妈的缝纫机坏了,她也拆开来修,大家都说她是“拆家小能手”。后来她上了大学,学了数学,第二次世界大战的时候,她加入了美国海军,成了第一批女程序员。
那时候的电脑是占满整个房间的庞然大物,叫“马克一号”(Mark I),重量有5吨,有500英里长的电线,17000个继电器,运算速度还没有你现在用的计算器快。程序员要给电脑下指令,得先在一张张打孔卡上打孔,一个孔代表一个0或者1,一张卡只能写一行指令,写错一个孔整张卡就废了。那时候程序员写一个简单的程序,要花好几个星期打孔卡,要是有一个错,就要全部重来,大家都特别头疼。
格蕾丝奶奶就想:“电脑能不能自己读懂人类写的语言呢?就像人翻译外语一样,把人类写的接近英语的指令,翻译成电脑能懂的0和1?” 当时所有人都觉得她疯了,说“电脑只会算数,怎么可能懂语言?你这是在浪费时间!” 但是格蕾丝奶奶没有放弃,她花了3年时间,带着团队写了几十万行代码,终于在1952年发明了世界上第一个编译器,叫“A-0系统”。
这个编译器一出来,程序员写代码的效率提高了100倍以上:原来写一段代码要几周,现在只要几个小时。后来格蕾丝奶奶又发明了我们现在还在用的COBOL编程语言,这种语言特别接近人类的英语,很多银行、政府的系统到现在还在用COBOL写的代码。她还发明了我们现在常说的“程序调试”(Debug)这个词:有一次她的电脑出问题了,她拆开发现是一只小虫子飞进了继电器里,把电路卡住了,她把这只小虫子抓出来,放在工作日志里,写了一句“First actual case of bug being found”(第一个发现bug的实际案例),后来大家就把电脑程序的错误叫“Bug”,找错误的过程叫“Debug”。
> 🌟 小挑战 / Little Challenge > 格蕾丝奶奶的故事里有很多“翻译”的例子,快找一找: > 1. 格蕾丝奶奶把人类的英语指令翻译成电脑的0和1,这里的源语言是____,目标语言是____,翻译官是____。 > 2. 你妈妈把菜谱上的“少许盐”翻译成“3克盐”,这里的源语言是____,目标语言是____,翻译官是____。 > 3. 你把中文儿歌翻译成英文给外国小朋友唱,这里的源语言是____,目标语言是____,翻译官是____。
我是小编译器:动手小实验
现在我们来玩一个“我是小编译器”的游戏,你不需要电脑,只需要一张纸和一支笔,就能体验编译器的工作过程: