我们使用文字编辑器编写完源代码后,就要调用C编译器把源代码转换成机器码。编译器处理翻译单元。一个翻译单元包含一个源代码文件。以及利用#include指令包含的所有头文件。如果此编译器单元内没有找到错误,就会产生目标文件(object file)后者包含对应的机器码。对象文件的扩展名为.o或者.obj。除此之外,编译器也可能会产生一个汇编器列表(汇编器列表会在后面介绍到)
目标文件也称为模块。一个链接库包含编译好的,可以快速存取的多个模块,里面有许多函数。
编译器将C程序的每个翻译单元(每个源代码文件包含的头文件)翻译成一个独立的目标文件。然后编译器调用连接器将所有的目标文件和所要用到的链接库函数结合起来成为一个执行文件。此执行过程内也包含目标操作系统需要加载和启动此程序所需要的信息。
从源代码到可执行文件的过程
编译程序有以下几个重要的逻辑步骤:
1.从源代码中读取字符,如果有必要的话,将字符转换成源代码字符集的字符。如果源代码中的行尾(end of line)字符和换行字符(new line character)不一样,就进行替换。类似的任何三字符组符号会被替换成对应的单一字符。(然而,双字符组不再这里做处理,不会替换成对应的单一字符。)
2.不管什么时候,只要反斜线符号后面紧跟着换行字符,预处理器就会将两者(反斜线和换行字符)都删除。因为预处理器的指令结束的地方就是行的结束字符,所以这个处理步骤会将反斜线放在一行的结尾处,以让预处理指令(比如宏的定义)在下一行继续
3.此源代码文件被分解成预处理器的记号和空格符。每个注释都被视为一个空格
4.预处理器指令被执行,宏被展开。
5.“字符常量”和“字符串字面值”内的字符和转义符,会被转换成“运行字符集”中对应的字符。
6.相邻的字符串字面值被串接成一个字符串。
7.实际的编译工作开始:编译器分析极好的序列,并产生对应的机器码
8.链接器解析对外部对象和函数的作用,并产生可执行文件。如果模块引用的外部对象或函数没有被定义在任何翻译单元中,链接器就会从外部的标准链接库或其他指定的链接库中复制他们,在一个程序中,一定不能多次定义外部对象和函数。
对于大多数的编译器来说,预处理器可以是一个独立的程序,或者编译器提供选项只进行预处理(而不编译),也就是步骤1到4.这样的话,就可以检查所编写预处理指令是否达到想要的效果。
这里再详细说说记号:
记号可以是关键字,标识符,常量,字符串字面值或者符号,C语言的符号包含一个或者多个标点字符,以及作为运算符或者双字符组的函数,或者具有语义上的重要性,比如分号表示一个简单语句的结束,大括号{}表示一个语句块。下面的语句半酣5个记号:
printf("Hello,world.\n"); |
也可以写作:
printf
(
"Hello,world.\n"
)
; |
被预处理器解释的记号,在翻译的第三阶段会分析。这只是翻译第七阶段的记号有一点点不同:
在#include指令中,预处理器标记额外的记号
和"filename".
在预处理阶段,字符常量和字符串字面值尚未从“源代码字符集”转换成“运行字符集”。
预处理器不区分整数常量和浮点数常量,这一点和编译器不同
当把源代码文件分析成记号的时候,编译器(或预处理器)总是采用下面的原则:每个后续的非空格符必须被附加到正在读入的记号后面,除非这么做会造成有效的记号变得无效。这个原则可以解决后面表达式的任何歧义之处,例如: a+++b
因为第一个+无法被当作标识符的一部分,也不能被当作以a开头的关键字的一部分,所以这个+是一个新记号的开始。第二个+附加到第一个的后面,形成一个有效的记号(也就是++),但是第三个+如果再附加上来,就不是有效的记号。因此这个表达式必须被分析为:
a ++ + b
在后面还会详细的介绍如何编译C程序
阅读(4595) | 评论(0) | 转发(0) |