汇编器
复习
- 第一次呼吸:把 CPU、存储器、总线和外设合起来,让完整计算机自动运行
- 用汇编语言指挥计算机:用助记符代替难读的二进制机器指令
- 名字、标签与地址:用名字和标签引用数据与跳转目标,不再手工数地址
TL;DR
- 汇编器负责把汇编翻译成机器码
- 因为存在“先引用、后定义”的标签,一遍往往不够
- 两遍扫描:第一遍分配地址、记录标签,第二遍查表、生成机器码
- 符号表是汇编器的核心:它记住每个名字对应哪个地址
正文
现在,把“用名字写程序”这件事交给机器: 汇编器 (assembler)读进汇编,吐出机器码。
听起来简单,可它一上手就遇到一个麻烦。
一遍为什么不够
看这段程序:
LOAD A, [x]
ADD R, A, B
JZ done
...
done: HALT
读到 JZ done 这一行时,汇编器还不知道 done 在哪里——它在后面才出现。
如果它一边读一边翻译,遇到 done 就只能干瞪眼。这种“先用后定义”,叫 前向引用 。名字带来方便,也带来了这个不大不小的麻烦。
好在办法很朴素: 先整体看一遍,再动手翻译。
第一遍:登记地址
第一遍从头到尾扫过去,只做两件事:
- 给每一行算出它会被放在哪个地址
- 遇到标签,就把“名字 → 地址”记到一张表里
这张表,叫 符号表 (symbol table)。以上一章那份数据程序为例,假使每条指令、每个数据都占一个存储单元,第一遍会得到:
| 名字 | 地址 |
|---|---|
start | 0 |
x | 5 |
y | 6 |
sum | 7 |
done | 4 |
这一遍不产出机器码,只把“谁在哪里”摸清楚。
第二遍:翻译与回填
第二遍再扫一遍。这回,汇编器一边读,一边把每一行翻译成机器码;一旦遇到名字,就查符号表,把地址填进去:
| 汇编 | 查表结果 | 机器码 |
|---|---|---|
LOAD A, [x] | x = 5 | 0001 00000101 |
LOAD B, [y] | y = 6 | 0010 00000110 |
ADD R, A, B | 无名字 | 0011 00000000 |
STORE [sum], R | sum = 7 | 0100 00000111 |
JZ done | done = 4 | 0110 00000100 |
HALT | 无名字 | 0111 00000000 |
第一遍登记,第二遍回填——标签先引用、后定义的问题,就这样被化解了。两遍扫描,也是许多翻译程序共用的套路。
它其实在做什么
把汇编器的工作抽象一下,无非是:
- 认识一套文法:哪一行是标签,哪一行是指令,哪一行是数据
- 维护一张符号表:名字和地址的对照
- 按规则输出机器码
这不就是一个“小型翻译器”吗?后面讲高级语言时,我们会遇到更大的翻译器—— 编译器 。它会用上同样的一些思路,只不过要翻译的东西复杂得多。
程序已经能用名字写、能被自动翻译了。但说来说去,我们还在一条一条地指挥机器。能不能换一种方式,让程序员只描述“想做什么”,而把“怎么做”留给机器?下一章,我们跨出这一步。
思考题
如果把程序里所有标签改成别的名字,符号表会变,但最终生成的机器码应该变吗?再想想:如果不先扫一遍就翻译,汇编器会在哪里卡住?
小结
知识点
- 汇编器把汇编翻译成机器码
- 前向引用:先使用、后定义的标签
- 两遍扫描:第一遍分配地址与登记符号,第二遍翻译与回填
- 符号表:名字与地址的对照
参考资料
- Wikipedia(zh):汇编器:汇编器的工作原理
- Wikipedia(zh):符号表:名字到地址的映射
- 《深入理解计算机系统》第 7 章:链接(了解符号与地址)
思考题答案(仅供参考)
机器码不该变。名字只是地址的别名,换个名字,地址不变,翻译结果自然也不变。如果只扫一遍就翻译,汇编器在遇到“后面才定义”的标签时会无从下手:标签的地址还没算出来,引用它的指令就没法填地址。这正是需要第一遍先登记符号的原因。
协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
封面图
设计师 | 南国微雪