通常的开发环境都是集成开发环境(IDE):编译链接一步完成(构建) gcc main.c
被隐藏的过程:
预处理(预编译)
主要处理以#开头的预编译指令
- 将所有的“#define”删除,并展开所有的宏定义
- 处理所有条件预编译指令,如“#if”、“#ifdef”、“#elif”、“#else”、“#endif”
- 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的(被包含的文件可能还包含其他文件)
- 删除所有注释“//”和“/**/”
- 添加行号和文件名标识,比如#2“main.c”2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告是能够显示行号
- 保留所有的“#pragma”编译器指令。因为编译器需要他们
生成“.i”文件(不包含宏定义)
命令:gcc -E main.c -o main.i
注意:若无法判断宏定义是否正确或头文件包含是否正确。查看预编译生成的“.i”文件。
编译
把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编代码文件
- 扫描。扫描器词法分析。 有限状态机 字符序列分割 记号 标识符、符号表 字面量、文字表
记号:关键词、标识符、字面量(数字、字符串)、特殊符号(+,-)
程序lex可以实现词法分析。改变词法规则
- 语法分析。产生语法树(以表达式为节点的树),采用上下文无关语法的分析手段
工具yacc可以实现语法分析。改变语法规则
- 语义分析。分析静态语义(编译期可以确定的语义)。语法树的表达式被标识类型,隐式转换插入相应的转换节点。
申明和类型的匹配,类型的转换
- 源代码优化。生成中间语言。将语法树转换成中间代码。
类型:三码地址,P-代码(P-Code)
- 目标代码生成。将中间代码转换为目标机器代码
- 目标代码和优化。选择适合的寻址方式、使用位移来替换乘法运算(基址比例变址寻址的lea指令,后mov指令完成赋值操作)、删除多余的指令
生成“.s”文件
命令:gcc -S main.i -o main.s
注意:实际上gcc这个命令只是后台程序的包装,他会根据不同的参数去调用预编译编译程序cll、汇编器as、链接器ld
汇编
将汇编代码转变为机器可以执行的指令。每一条汇编语言几乎都对应一条机器指令。(根据汇编指令和机器指令的对照表一一翻译)
命令:gcc -c main.s -o mian.o
生成“.o”文件(目标文件)
链接 (静态链接)
把每个源代码模块独立编译,然后按须要将他们“组装”起来。即把一些指令对其他符号地址的引用加以修正。
- 地址和空间分配
- 符号决议
- 重定位。地址修正的过程。每个要被修正的地方叫一个重定位入口。
命令:gcc main.o -o main
目标文件和库(常见库:运行时库)链接生成可执行文件.out
补充:
链接过程的必要性:连接之前的过程都是以单个源文件为单元进行操作,但是项目往往是由多文件合成,单个文件如何去访问其他文件,这就由链接来完成。
链接过程解决了前几步骤的遗留问题。1、弱符号(注意,弱符号只存在于C语言中),2、符号表,外部符号,3、指令段、虚假的偏移和地址。
链接完成的功能:1、合并段和符号表,2、符号解析(*UND*区域),3、分配地址和空间,4、符号的重定位
连接完成的文件并不是可直接运行的,缺乏CPU的环境,还需要经过运行阶段配置好环境才可以直接运行。
运行阶段:
1、建立虚拟地址空间和物理内存的映射关系。(配置PCB)
2、j加载指令和数据。
3、入口地址写入下一行指令寄存器。