开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
C++程序编译主要经历四个阶段:预编译、编译、汇编与链接,下面将对各个阶段做详细介绍
预编译
在预编译过程中主要处理以#开头的预编译指令,并将.o文件生成.i文件
命令语句:gcc -E 文件.c -o 文件.i
执行的工作:
- 展开宏定义:删除
#define并进行替换 - 处理条件预编译指令,如
#ifdef、#endif - 处理
#include预编译指令,将文件内容替换到相应的位置 - 删除注释
- 保留
#pragma编译器指令,编译器需要使用到,如#pragma once是为了防止文件重包含 - 添加行号和文件标识
编译
将预编译后的文件进行词法分析、语法分析、语义分析及优化后生成.s汇编代码文件
命令语句:gcc -S 文件.i -o 文件.s
执行的工作:
- 词法分析:利用类似的有限状态机算法,通过扫描器将源代码程序中的字符序列分割成一系列的记号
- 语法分析:语法分析器对由扫描器产生的记号进行语法分析,产生语法树(对表达式语法层面的分析)
- 语义分析:语义分析器对表达式是否有意义进行判断,其分析的语义是静态语义(在编译期能分析的语义,动态语义是在运行期才能确定的语义)
- 优化:对源代码进行优化
- 目标代码生成:由代码生成器将源代码转换成目标机器代码(用汇编语言表示)
- 目标代码优化:目标代码优化器对目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等
汇编
将汇编代码文件翻译为目标文件(Windows下为.obj文件,Linux下为.o文件)
命令语句:gcc -c 文件.s -o 文件.o
执行的工作:汇编器as根据汇编指令和机器指令的对照表一一翻译
目标文件有三种类型:
- 可执行目标文件:可以直接在内存中执行
- 可重定位目标文件:需要与其他目标文件链接到一起形成可执行目标文件
- 共享目标文件:是特殊的可重定位目标文件,可以在运行时被动态加载进内存进行链接
由于可重定位目标文件需要和其他目标文件链接到一起,所以有第四步的链接过程
链接
将目标文件链接为可执行文件
命令语句:gcc 文件.o -o 文件
执行的工作:由链接器完成
- 符号解析:将每个符号引用与符号定义关联
- 重定位:链接器将每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使它们指向这个内存位置
链接有两种形式:静态链接与动态链接
静态链接
由链接器在链接时将静态链接库中的内容添加到可执行程序中
静态链接库(Windows下为.lib后缀,Linux下为.a后缀)
优点
- 执行时运行速度快,可执行程序具有程序运行时所需要的所有东西
- 只需保证在开发者的计算机中有正确的.lib文件,在以二进制形式发布程序时不需考虑在用户的计算机上.lib文件是否存在及版本问题
缺点
- 浪费空间:每个可执行程序都会有静态链接库的一个副本
- 更新困难:当链接库的代码修改了就需要重新编译链接
动态链接
链接器只在最终的可执行程序中记录了动态链接库的名字等一些信息,可执行程序执行时动态链接库的内容会被映射到进程的虚拟地址空间中
动态链接库(Windows下位.dll后缀,Linux下为.so后缀)
优点
- 节省空间:多个程序在执行时共享同一份副本
- 更新方便:修改库文件的时候不需要重新编译链接,新版本的库文件会自动加载到内存进行链接
缺点
- 每次执行都需要链接,执行速度慢