Preprocessors
预处理器为编译器产生输入。可能执行:
- Macro processing。允许为长的构造定义短的宏
- File inclusion .把头文件包含到程序文本
- "Rational " precessors 。
- Language extensions.这些处理器试图向语言加入新的能力。例如Qquel是数据库查询语言嵌入到C。##开始的语句由预处理器处理,翻译为执行数据库存取的例程。
- 宏定义-一般有某些唯一的字符或关键字指定,例如define或者macro,通常包含
- 被定义的宏的名字
- 一个body
- 通常,宏预处理器允许在它们的定义中使用形式参数formal parameters,也就是将被值替换的符号(值在这个上下文中,是一个字符串)
- 宏使用提供宏的名字和实际参数actual parameters,也就是形式参数的值。
- 宏处理器用实际参数替换body中的形式参数
- 转换后的body代替了宏本身
Example 1.2 TEX字处理系统包含一个通用的宏设施。宏定义的格式如下:
\define <macro name> <template> {<body>}
宏的名字是任何字母字符串前面加反斜杠。template模版是任何字符串,其中#1,#2,....#9被看作形式参数。这些符号也出现在body中,可以任意次数。例如,下面的宏定义对Journal of the ACM的一个引用:
\define\JACM #1;#2;#3.
{{\sl J. ACM} {\bf #1}:#2, pp. #3.}
宏的名字是\JACM,模版是"#1;#2;#3." ;把参数分开,最后的参数后面跟一个句号。
使用这个宏取template的形式,除了用任意字符串替换形式参数。因此,我们可以写:
\JACM 17;4;715-728.
那么就能看到:
J.ACM 17:4 pp. 715-728
body中{\s1 J. ACM}部分表示要对J.ACM进行斜体。表达式{\bf #1}说第一个参数应该粗体,这个参数用作volumn号。
TEX允许在\JACM的定义中使用任何标点符号或文本字符串分隔volumn, issue和page number。我们甚至可以根本不用标点符号。此时TEXT会取每一个实际参数为一个字符或者一个{}括起来的字符串。
Assembles
某些编译器产生汇编代码,如(1.5),然后把它传给一个汇编器进一步处理。 其他编译器执行汇编器的工作,产生可重定位的机器代码,可以直接传输给loader/link-editor。
汇编代码和机器代码的关系:
Assembly code 汇编代码是机器码的助记缩写版本,其中用名字代替操作的二进制代码,内存的地址也用名字代替。一个典型的汇编指令序列可能是:
MOV a, R1
ADD #2, R1
MOV R1, b (1.6)
这份代码把地址a的值移入寄存器1,然后对它加上常量2,把寄存器1的内容作为一个定点数字,最后把结果保存到b命名的位置。因此,它计算b := a + 2.
Two-Pass Assembly
最简单的汇编器在输入上进行两遍,其中一pass遍对输入文件读取一次。
第一遍,所有指定存储位置的标识符被发现,并且保存到符号表(和编译器无关)。第一次碰到标识符时,为它们分配存储位置,因此例如在读取(1.6)以后,符号表可能包含如Fig. 1.12这些条目。 
此图中,我们假设一个WORD,包含4个字节,为每一个标识符分配,地址从0开始分配- 第2遍,汇编器再次检查输入。这一次,它把每一个操作代码翻译为该操作在机器语言中的bit序列,同时把每一个代表一个位置的标识符翻译为符号表中登记的该标识符的地址。
- 第2遍的输出通常是relocatable可重定位的机器代码,意味着它可以从任何位置L开始加载:也就是说,L被加到代码中的所有地址,那么所有的引用都能够正确了。因此,汇编器的输出必须区分指令中引用到地址的部分
- Example 1.3 下面是1.6翻译后假想的机器代码
0001 01 00 00000000 *
0011 01 10 00000010
0010 01 00 00000100 * (1.7)
我们预想一个很小的指令字:- 其中前面4位是指令代码,0001,0010和0011各自代表load,store和add。load和store意味着从内存移到寄存器或相反;
- 接下去的两位指定一个寄存器,01指三条指令中的寄存器1.
- 这之后的两位代表一个'tag'
- 其中00代表普通地址模式,后8位指向内存地址。
- tag 10代表"立即"immediate模式,最后8为取作操作数的字面意义。该模式出现在(1.7)的第2条指令中。
- (1.7)的第1行和第3行有1个*,*代表可重定位的位relocation bit,和可重定位机器代码的每一个操作数相关。假设包含数据的地址空间被加载到起始位置L。那么*的存在表示L必须加到指令的地址上。因此,如果L = 00001111,也就是15,那么a和b分别在位置15和19。(1.7)的指令变为:
0001 01 00 00001111
0011 01 10 00000010
0010 01 00 00010011 (1.8)
这是绝对的,或者不可重定位的机器代码。注意(1.7)的第2条指令没有*,因此L也不会加到它的地址上,这当然是正确的,因为这些bit代表的是常量2,而不是位置2。
Loaders and Link-Editors
通常一个程序loader执行loading和link-editing两个功能。
- 加载的过程包括取可重定位的机器代码,按照前面讨论的方法改变可重定位的地址,替换内存中适当位置被改变的指令和数据。
- 连接编辑器允许我们用多个可重定位机器代码文件组成一个程序。这些文件可能是不同编译的结果,以及系统提供例程的一个或多个库文件。
- 这种引用可能是在一个文件中定义的数据位置,在另外一个文件中使用
- 或者可能是一个过程的入口点出现在一个文件的代码中,在另一个文件中对它进行调用
可重定位的机器代码文件必须为每一个被外部引用的数据位置或指令标签保留符号表中的信息。如果我们事先不知道什么可能被引用,那么我们必须包含整个汇编器符号表,作为机器代码的一部分。
例如,代码(1.7)可能产生
a 0
b 4
如果一个和(1.7)一起加载的文件引用(1.7)b,那么引用将被4代替,加上文件(1.7)被重定位时的数据位置偏移。
没有评论:
发表评论