Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

汇编器

复习

  • 第一次呼吸:把 CPU、存储器、总线和外设合起来,让完整计算机自动运行
  • 用汇编语言指挥计算机:用助记符代替难读的二进制机器指令
  • 名字、标签与地址:用名字和标签引用数据与跳转目标,不再手工数地址

TL;DR

  • 汇编器负责把汇编翻译成机器码
  • 因为存在“先引用、后定义”的标签,一遍往往不够
  • 两遍扫描:第一遍分配地址、记录标签,第二遍查表、生成机器码
  • 符号表是汇编器的核心:它记住每个名字对应哪个地址

正文

  现在,把“用名字写程序”这件事交给机器: 汇编器 (assembler)读进汇编,吐出机器码。

  听起来简单,可它一上手就遇到一个麻烦。

一遍为什么不够

  看这段程序:

      LOAD  A, [x]
      ADD   R, A, B
      JZ    done
      ...
done: HALT

  读到 JZ done 这一行时,汇编器还不知道 done 在哪里——它在后面才出现。

  如果它一边读一边翻译,遇到 done 就只能干瞪眼。这种“先用后定义”,叫 前向引用 。名字带来方便,也带来了这个不大不小的麻烦。

  好在办法很朴素: 先整体看一遍,再动手翻译。

第一遍:登记地址

  第一遍从头到尾扫过去,只做两件事:

  1. 给每一行算出它会被放在哪个地址
  2. 遇到标签,就把“名字 → 地址”记到一张表里

  这张表,叫 符号表 (symbol table)。以上一章那份数据程序为例,假使每条指令、每个数据都占一个存储单元,第一遍会得到:

名字地址
start0
x5
y6
sum7
done4

  这一遍不产出机器码,只把“谁在哪里”摸清楚。

第二遍:翻译与回填

  第二遍再扫一遍。这回,汇编器一边读,一边把每一行翻译成机器码;一旦遇到名字,就查符号表,把地址填进去:

汇编查表结果机器码
LOAD A, [x]x = 50001 00000101
LOAD B, [y]y = 60010 00000110
ADD R, A, B无名字0011 00000000
STORE [sum], Rsum = 70100 00000111
JZ donedone = 40110 00000100
HALT无名字0111 00000000

  第一遍登记,第二遍回填——标签先引用、后定义的问题,就这样被化解了。两遍扫描,也是许多翻译程序共用的套路。

它其实在做什么

  把汇编器的工作抽象一下,无非是:

  • 认识一套文法:哪一行是标签,哪一行是指令,哪一行是数据
  • 维护一张符号表:名字和地址的对照
  • 按规则输出机器码

  这不就是一个“小型翻译器”吗?后面讲高级语言时,我们会遇到更大的翻译器—— 编译器 。它会用上同样的一些思路,只不过要翻译的东西复杂得多。

  程序已经能用名字写、能被自动翻译了。但说来说去,我们还在一条一条地指挥机器。能不能换一种方式,让程序员只描述“想做什么”,而把“怎么做”留给机器?下一章,我们跨出这一步。

思考题

  如果把程序里所有标签改成别的名字,符号表会变,但最终生成的机器码应该变吗?再想想:如果不先扫一遍就翻译,汇编器会在哪里卡住?

小结

知识点

  • 汇编器把汇编翻译成机器码
  • 前向引用:先使用、后定义的标签
  • 两遍扫描:第一遍分配地址与登记符号,第二遍翻译与回填
  • 符号表:名字与地址的对照

参考资料

  1. Wikipedia(zh):汇编器:汇编器的工作原理
  2. Wikipedia(zh):符号表:名字到地址的映射
  3. 《深入理解计算机系统》第 7 章:链接(了解符号与地址)

思考题答案(仅供参考)

  机器码不该变。名字只是地址的别名,换个名字,地址不变,翻译结果自然也不变。如果只扫一遍就翻译,汇编器在遇到“后面才定义”的标签时会无从下手:标签的地址还没算出来,引用它的指令就没法填地址。这正是需要第一遍先登记符号的原因。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪