C++ 编译原理及优化编译
本文回归了 C++ 的编译过程,了解 C++ 编译的优化方法
C++ 编译过程?
- (1) 预处理器:读取 C++ 源代码,对其中的伪指令和特殊符号进行处理
- (2) 编译器: 主要做词法分析、语法分析、语义分析等,在检查无错误后后,把代码翻译成汇编语言
- (3) 汇编器: 将汇编代码转变成机器可以执行的指令 (机器码文件)
- (4) 链接器: 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序
- 经过以上编译,C++ 源码被编译为可执行程序
1
2
3
4g++ -E main.cpp > main.i # 预处理器:进行预编译,并将预编译后的代码保存到main.i上
g++ -S main.i # 编译器:将main.i编译成汇编,并默认保存为main.s
g++ -c main.s # 汇编器:将main.s翻译成目标机器指令,默认名字为main.o
g++ -o main main.o # 链接器:将main.o,以及相关的库目标 链接为可执行文件main - 大多数的 C++ 编译器并不在乎源文件的扩展名,但是如果您未指定扩展名,则默认使用 .cpp
- 最常用的免费可用的编译器是 GNU 的 C/C++ 编译器,如果您使用的是 HP 或 Solaris,则可以使用各自操作系统上的编译器
C++ 编译的预处理器?
- 宏定义替换,头文件展开,条件编译展开,删除注释
- C/C 预处理不做任何语法检查,不仅是因为它不具备语法检查功能,也因为预处理命令不属于 C/C 语句,语法检查是编译器要做的事情
- gcc -E 选项可以得到预处理后的结果,扩展名为.i 或 .ii
C++ 编译的编译器?
- 生成汇编代码,得到汇编语言程序,该种语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令
- gcc -S 选项可以得到编译后的汇编代码文件,扩展名为.s
- 汇编语言为不同高级语言的不同编译器提供了通用的输出语言
C++ 编译的汇编器?
- 生成目标文件
- gcc -S 选项可以得到编译后的汇编代码文件,扩展名为.s
C++ 编译的链接器?
- 生成可执行文件或库文件
- 静态库: 指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,其后缀名一般为 “.a”
- 动态库: 在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可执行文件比较小,动态库一般后缀名为 “.so”
- 可执行文件: 将所有的二进制文件链接起来融合成一个可执行程序,不管这些文件是目标二进制文件还是库二进制文件
C++ 编译的特点?
- 每个源文件独立编译
- 每个编译单元,都需要独立解析所有包含的头文件
- C/C++ 跨编译单元的优化只能交给链接器
GCC 编译提供多少种优化方法?
- 精简操作指令
- 尽量满足 CPU 的流水操作
- 通过对程序行为地猜测,重新调整代码的执行顺序
- 充分使用寄存器
- 对简单的调用进行展开
GCC 编译提供哪些优化级别?
- GCC 提供了从 O0-O3 以及 Os 这几种不同的优化级别供大家选择,在这些选项中,包含了大部分有效的编译优化选项,并且可以在这个基础上,对某些选项进行屏蔽或添加,从而大大降低了使用的难度
- O0: 不做任何优化,这是默认的编译选项
- O 和 O1: 对程序做部分编译优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化
- O2: 是比 O1 更高级的选项,进行更多的优化。GCC 将执行几乎所有的不包含时间和空间折中的优化。当设置 O2 选项时,编译器并不进行循环展开以及函数内联优化。与 O1 比较而言,O2 优化增加了编译时间的基础上,提高了生成代码的执行效率
- O3: 在 O2 的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化
- Os: 主要是对代码大小的优化,通常各种优化都会打乱程序的结构,让调试工作变得无从着手。并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序的正确性
使用 GCC 编译优化的缺点?
- 调试问题:任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内 load/store 操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足
- 内存操作顺序改变问题:在 O2 优化后,编译器会对影响内存操作的执行顺序