标签:数字 编译程序 表达式 问题: 汇编 语言 常量 完成 执行文件
"hello world"可以说是所有程序员闭着眼睛都能写出来的代码:
#include <stdio.h> int main() { printf("hello world\n"); return 0; }
编译运行一气呵成。而每当有人问起:从源码到可执行程序有哪些步骤,大多数程序员面对这个问题也能脱口而出:预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。
不过很多人不了解其中都做了哪些处理,今天就带大家来好好聊一聊。
处理的第一步,是将源码文件.c和头文件.h编译成一个.i文件。
预编译过程主要是做了以下一些工作:
编译过程就是将预处理完的文件进行词法分析、语法分析、语义分析和优化后产生相应的汇编代码文件。
词法分析主要使用词法分析器(也叫扫描器),将源代码的字符序列分割成一系列的符号(Token)。比如如下一段程序:
int array = (index + 4) * 2;
经过扫描以后,产生11个记号:
int 关键字
array 标识符
= 赋值操作符
( 左小括号
index 标识符
+ 加号
4 数字
) 右小括号
* 乘号
2 数字
; 语句结束
语法分析产生的记号一般可以分为:关键字,标识符,字面量(包括数字和字符串等)和特殊符号(加号减号等)。在识别记号的同时,扫描器也完成其他工作,比如讲标识符存放到符号表,讲数字字符串常量存放到文字表,以备后面的步骤使用。
接下来语法分析器将对由扫描器产生的记号进行语法分析,从而产生语法树。整个分析过程采用了上下文无关语法的分析手段。
这个阶段由语义分析器来完成。语法分析仅仅完成了对表达式的语法层面的分析,他并不了解这个语句是不是真的有意义。比如两个指针相乘是没有意义的,但是在语法上是合法的。
编译器可以分析的语义是静态语义,即在编译器就可以确定的语义;与之对应的是动态语义,即在运行期才可以确定的语义。
静态语义通常包括声明和类型的匹配,类型的转换。比如一个浮点型表达式赋值给整形表达式的时候,语义分析会完成浮点型到整形的转换。动态语义使之运行期出现的语义相关问题,比如除数是0的时候会报运行期语义错误。
现代编译器有很多层的优化,往往在源代码级别会有一个优化过程。源代码优化器会在源码级别进行优化,比如一行代码:
array[index] = (index + 4) * (2 + 6);
在这行代码中,(2+6)这个表达式就可以被优化掉,因为他的值在编译器就可以确定。
在进行了语法分析和语义分析阶段的工作之后,有的编译程序将源程序变成一种内部表示形式,这种内部表示形式叫做中间语言或中间表示或中间代码。所谓“中间代码”是一种结构简单、含义明确的记号系统,这种记号系统复杂性介于源程序语言和机器语言之间,容易将它翻译成目标代码。
中间代码使得编译器可以分为前端和后端,前端负责产生机器无关的中间代码,编译器后端将中间代码转换成目标机器代码。这样对于一些跨平台的编程语言,他们可以针对不同平台使用同一个前端和针对不同平台的数个后端。
源码级优化器产生中间代码标志着下面的过程都属于编辑器的后端。编译器后端主要包括代码生成器和目标代码优化器。代码生成器将中间代码转换成目标机器代码,然后目标代码优化器进行代码优化,比如选择合适的寻址方式、食用为宜来代替乘法运算,删除多余的指令。
经过扫描、词法分析、语法分析、语义分析、源代码优化、代码生成和目标代码优化,源代码终于被编译成了目标代码。但是现在还有一个问题:目标代码中有的变量定义在其他模块,我们该怎么办?
事实上,定义在其他模块的变量和函数在最终运行时的绝对地址都要在链接的时候才能确定。所以现代编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。
暂时挖个坑
标签:数字 编译程序 表达式 问题: 汇编 语言 常量 完成 执行文件
原文地址:https://www.cnblogs.com/fensi/p/13155019.html