分类:
2011-06-07 18:58:43
原文地址:GCC(v4.1.2)编译器分析 作者:fireaxe
fireaxe:趁着放假,翻出了几年前写的一些文档,很多是翻译的英文资料,但对当时的我都是全新的东西,留着作纪念吧 0 前言
本文个编译选项均在windriver提供的ccppc编译器(GCC v4.1版本)验证通过。
文中多数选项适用于GCC各版本的编译器,某些选项在不同版本中是有区别的,比如优化开关-O系列选项。如果需要在非4.1版本的GNU编译器上使用本书列出的选项,请参考相关文档。
1 名词解释下面列出了文中用到的一些名词:
definition 定义 reference 声明 |
2 预编译宏定义
GNU编译器预先定义了大量的宏定义,被称为预编译宏。我们能够通过命令“cpp –E –dM myprog.c”查看定义了那些预编译宏。下面列出了一些重用的宏:
2.1 __cplusplus:当源码是C++时会被定义。标准定义下,它是包含年月日的字符串,否则它被定义为1。
2.2 __DATE__:一个11个字符的字符串,格式为“May 3 2002”
2.3 __TIME__:一个9个字符的字符串,格式为“18:10:34”
2.4 __GNUC__:用于判断编译器是否为GNU编译器,格式为GNU主版本号,如版本号3.1.2,则这个宏的定义为3。
2.5 _GNUC_MINOR__:格式为GNU小版本号,如版本号3.1.2,则这个宏的定义为1。
2.6 __GNUC_PATCHLEVEL__:为GNU修订号。如版本号3.1.2,则这个宏的定义为2。
2.7 __LINE__:行号
2.8 __VERSION__:完整的版本号,至少包含主版本号与小版本号
3 编译选项
编译选项可用于gcc编译器的编译过程,通常ppc的编译器为ccppc。所有编译选项都必须以连字符开始。绝大多数编译选项都有前缀,下面以不同的前缀分类介绍编译选项。
3.1 前缀“-”-常用的打开编译选项的方法是一个连字符加上一个字母。现在出现了另一种加选项的方法,两个连字符加上编译选项。许多选项具有以上两种形式,但做的是相同的事。例如:-g与—debug其实是一种编译选项的两种写法。
3.1.1 -g以默认形式产生调试信息。GDB能够利用这些调试信息工作。
在多数情况下,-g使用stabs格式。-g会使用一些额外的调试信息。利用这些额外信息,GDB可以更好的工作,但其它调试器可能会因此而无法工作。如果需要指定调试信息的格式,可以使用带参数的-g命令。如:-gdb,-gstabs,-gstabs+。
3.1.2 -ansi该命令强制要求编译器按照标准(standard)编译代码。产生的指令不能与标准冲突,但可以使用那些不冲突的GNU扩展。默认情况下,GCC4.1.2编译器对C语言默认ISO C90标准;对C++语言默认支持ISO C++标准。
ansi命令的问题是它的标准是跟编译器有关的。比如GCC2.95版使用的是ISO C89标准。如果需要指定某个特定标准,可以使用-std命令。如果既不使用-ansi,又不使用-std,则默认为-std=gnu89标准。
3.1.3 -std该命令指定某种特定的语言标准(目前只适用于C/C++语言)。常见的标准如下:
c89:ISO C89标准
iso9899:1990:ISO C90 ( 相当于是 “-ansi”)
c99:ISO C99标准
gnu89:默认配置,ISO C90加GNU扩展(包含部分C99特性,如在C中使用注释符“\\”)
gnu99:ISO C99加GNU扩展标准。随着GCC对ISO C99的特性越来越多的支持,gnu99将会逐步变为默认配置。
3.1.4 -E只对源码进行预编译。输出以经过预编译的源码的形式显示到标准输出上。不需要预处理的代码会被忽略。
当是由编译选项 –E时,可以配合其它一些选项使用,他们是:-dD, -dI, -dM, -dN, -C, -P。
3.1.5 –llibrary寻找以library命名的库文件并连接入目标文件。该库文件名字格式为liblibrary.a。例如:-ltest会在连接时寻找库文件libtest.a。使用-l与直接指定一个文件的唯一区别在于-l会在library变为liblibrary.a的名字然后寻找,而且会寻找多个文件夹。-l指定的库会在系统的几个标准文件夹与我们用-L指定的文件夹内搜索。
3.1.6 -Ldir为-l指定的库文件添加搜索路径。
3.1.7 -Idir添加头文件搜索路径dir。
通过-I选项添加的路径会的搜索优先级高于任何其它搜索路径(包括系统默认路径)。以-I指定的多个路径按先后顺序依次搜索。
需要注意的是风河的编译器ccppc好像不会指定标准库,必须要通过-I去指定。
3.1.8 -iquotedir为以引号引入的头文件添加搜索路径dir。与-I的区别在于只有“#include “file””这样的头文件才会在dir中搜索,“#include
定义一个预定义宏,如果没有“[=string]”,则默认定义宏为1。相当于在源码中加入:
#define macro string
与之相反的选项是-U用于取消宏定义。
3.1.10-Umacro取消一个宏定义,相当于在源码中加入:
#undef macro
3.2 前缀“-f”
f代表flag,也就是标志位。
大多数此类选项有 “开”与“关”两种状态。每个这样的标志位选项以加“no-”前缀做为关闭状态,而以不加“no-”做为打开状态。例如:-fstrict-aliasing代表打开“严格的别名检查”;-fno-strict-aliasing代表关闭“严格的别名检查”。
绝大部分此类选项只有打开关闭两种属性,因此会由其中一种做为默认配置。但也由一小部分选项不是这样。例如,下边的两个选项被用来指定在for循环中声明的变量的范围:
-ffor-scape -fno-for-scape
这两种都不是默认设置,默认配置与标准有关,这两个选项都是默认配置的变种,因此可以说有三种选择。
所有的-f选项都可以被双连字符前缀代替。如下两个选项是相同的:
-frtti --rtti
注意,下面列出了部分选项,但并不是所有的,特别是没有包含O的各级别可能会包含的选项。那些选项会在优化那一章中讲解。
3.2.1 dollars-in-identifiers3.2.2
3.3 前缀“-m”
m代表machine,这一类命令代表与架构有关的编译选项(Machine-Specific Compiler Options)。
此类选项的大部分都是与特殊的平台(specific platform)有关,都是用来为在某一类平台上所具有的特性产生特殊的代码结构。但也由一选项用于调试目的,或者用来组织目标文件(object files)的段结构。例如,选项“-msdata”用于在目标文件中生成小数据段(small data segment)
注:对于这些与特殊平台有关的选项,本文不会涉及太多,只是对一些常用的命令做些说明。
3.3.1 –mcpu=83133.3.2 strict-align
严格的边界检查,不允许做不对齐的访问。该选项是默认配置。
no-strict-align可以关闭该选项
3.4 前缀“-W”
W代表Warning。
-W选项被用来指定编译器会对那类情况产生Warning。与-f一样,此类选项也具有加前缀“no-”与不加前缀“no-”两类情况。例如,下面的选项将使编译器对函数入参过多情况产生警告(估计与cpu架构有关,powerpc应该会允许8个入参而不会产生警告):
-Wformat-extra-args
下面的选项将允许传入过多入参而不产生告警。
-Wno-format-extra-args
3.4.1 -Wall打开所有告警。要注意很多告警其实是默认打开的,而另一类告警根本没有选项去控制,。
3.4.2 -Wcomments当“/*”紧跟在“/*”之后时,或“//”后有反斜杠“\”时会有告警。
3.4.3 -Werror把告警当作error来处理,这样当有告警时就会停止编译。
4 编译优化
指定编译器对代码的优化级别。编译器优化选项主要目的是优化代码大小与代码执行速度,而这两项通常是冲突的,因此优化级别的不同主要体现在对这两个因素的取舍上。默认的配置是“-O0”,也就是不优化。
如果使用优化选项-O0(这通常是默认配置),编译器生成的汇编码会与源码的结构匹配。O0优化的缺点是会占用较多内存,而且代码执行效率比较低。根据“存在的就是合理的”这句名言,如果O0优化没有优点的话也不会被各个版本的GNU编译器所支持了。O0优化由于没有进行代码优化,编译速度要快一些。更重要的是,O0优化不会改变代码结构。这两个因素决定了O0对于软件开发阶段来说是及其理想的,调试器可以很容易的跟踪代码执行过程。而其它优化选项,由于可能会重新排布代码顺序,跟踪起来要困难的多。
需要注意的一点是,下列的这些选项只是优化时所包含的一部优化选项,有些是没有列出的。
4.1 -O0 优化不优化,这也是默认配置
4.2 -O (-O1) 优化在O1优化中,编译器会试图减少代码大小与执行时间,但需要保证这些修改不会影响调试器的工作。下面列出了O1优化包含的编译选项:
4.2.1 defer-pop函数延迟出栈。
函数返回时,不会立刻把函数出栈,而是等到必要时再一起出栈,用于增加效率。与其相反的编译选项是no-defer-pop,该选项会再每次函数返回后立刻出栈。
经测试没有发现这个参数有明显作用。
4.2.2 delayed-branch该选项只在支持延迟分支点的机器上有效,现在使用的ppc603内核不支持该选项。
4.2.3 guess-branch-probability4.2.4 cprop-registers
4.2.5 loop-optimize
4.2.6 if系列
if-conversion
if-conversion2
4.2.7 tree系列
tree-ccp
tree-dce
tree-dominator-opts
tree-dse
tree-ter
tree-lrs
tree-sra
tree-copyrename
tree-fre
tree-ch
4.2.8 unit-at-a-time
4.2.9 merge-constants
4.3 -O2 优化
O2级别的优化会打开除了涉及空间与速度交换的几乎所有优化选项。它除了包含O1的所有选项外,还会打开下列的优化选项:
4.3.1 thread-jumps4.3.2 crossjumping
4.3.3 optimize-sibling-calls
4.3.4 cse系列
cse-follow-jumps
cse-skip-blocks
4.3.5 gcse系列
gcse
gcse-lm
4.3.6 expensive-optimizations
4.3.7 strength-reduce
4.3.8 return系列
rerun-cse-after-loop
rerun-loop-opt
4.3.9 caller-saves
4.3.10peephole2
4.3.11sche系列
schedule-insns
schedule-insns2
sched-interblock
sched-spec
4.3.12regmove
4.3.13strict-aliasing
4.3.14delete-null-pointer-checks
4.3.15reorder系列
reorder-blocks
reorder-functions
4.3.16内存对齐系列
align-functions
align-jumps
align-loops
align-labels
4.3.17tree系列
tree-vrp
tree-pre
4.4 -O3 优化
4.4.1 inline-functions
4.4.2 unswitch-loops
4.4.3 gcse-after-reload
4.5 -Os 优化
Os优化突出空间的优化,所以它会禁止掉O2优化中某些可能会牺牲空间性能的优化选项。
4.5.1 内存对齐系列align-functions
align-jumps
align-loops
align-labels
这些选项会强制编译后的代码内存对齐,这会提升访问速度,但会增加目标文件的大小,因此Os优化中会禁止这些选项。
4.5.2 reorder系列reorder-blocks
reorder-blocks-and-partition
4.5.3 prefetch-loop-arrays
4.5.4 tree-vect-loop-version
5 链接选项
链接选项用于链接器ld。对于ppc,我们使用ldppc。这些选项用于确定链接的过程于最终结果。
5.1 -X等价--discard-locals。删除所有的临时本地符号。(在汇编中可以看到这些变量是以“.L”为前缀的一些变量)。
5.2 -r等价--relocatable。产生可重定向输出。生成的输出文件能够做为ld的输入文件。这通常被称为partial linking。如果是C++文件,需要使用-Ur。
该选项生成的目标文件可以不包含库。通常会利用它生成一个不依赖与库的partial文件,然后在把它与各种库,包括sysTbl.o等文件一起链接为一个可执行文件(ELF文件)。
通俗的讲,使用了-r选项,编译器就不会再要求一个函数或变量必须有实现,只要有声明就可以了。这类输出文件不会是最终的目标文件,要么是需要下到已经有一个可执行文件在运行的目标机中与运行,要么还要与其它库文件或目标文件等链接成可执行文件。
5.3 –e MyEntry等价“--entry=MyEntry”,用于指定MyEntry为入口点。程序会以MyEntry做为入口点开始执行程序,而不是以默认的start做为入口点。
所谓入口点(Entry Point)是程序开始执行后的第一条指令。有下列四种方式指定入口点。链接器(linker)会顺序尝试一下四种方法,直到有一种成功为止:
(1) 通过-e入口指定命令选项指定入口点;
(2) 寻找符号名为start的函数;
(3) 以“.text”段的第一个字节做为入口点(也就是代码段);
(4) 以地址0做为入口点;
5.4 -Tsectionname org
等价与“--section-start .sectionname=org”,用于指定目标文件中指定段sectionname的起始地址为or(该地址为绝对地址,不可被重定向)。如果不制定段起始地址,则根据链接器的默认配置生成。可以多次调用该命令指定同一种段,执行结果是一种段被分为多个段。
org的格式必须是十六进制整数。为了于其它链接器兼容,不要在十六进制数前加前缀“0x”。例如,我想把代码段的起始地址指定为“0x13578bdf”,则命令格式为“-Ttext 13578bdf”。
常用的段有bss、data和text三种段,因此有如下三种命令形式(注意:第二种格式在段前有一个“.”):
|
简单格式 |
复杂格式 |
代码段 |
-Ttext org |
--section-start .text=org |
数据段 |
-Tdata org |
--section-start .data=org |
bss段 |
-Tbss org |
--section-start .bss=org |
5.5 -M
等价于“--print-map”。把链接图(link map)打印到标准输出上去。链接图包含如下信息:
(1) 目标文件放入内存后的分布情况,如代码段、数据段的起始地址等。
(2) 符号是如何分配的。
(3) 所有被链接进去的库文件,以及库中引入的符号。
(4) 符号的初始值。
链接图的具体信息请参考链接脚本的相关内容。
5.6 -Map mapfile与-M作用相似,但链接图会被打印到文件mapfile中。
5.7 -defsym5.8 --cref
输出交叉声明表。也就是符号与库文件的对应关系。
Cross Reference Table Symbol File Test test.o test test.o test1 test.o |
如果已经通过“-Map”指定了链接表输出路径,则把交叉声明表输出到连接表文件中;否则输出到标准输出。
从上面的表中可以看到,交叉声明表的格式非常简单,如果需要的话,可以很容易地被脚本(script)处理。符号按字母顺序排序。每一个符号跟一个文件列表,第一个文件是定义它的文件,后面的是声明它的文件。
5.9 --start-group archives --end-group按组包含库文件。archives应该是一组库文件(Archeive files),他们应该通过文件名显示加载或通过“-l”选项加载。
按组包含的库文件会在链接时被反复的查找未被未被定义的声明。通常,如果通过命令行为链接器传入库文件,他们谁被顺序包含,所以如果前一个库文件用到了后一个库文件的符号,会找不到符号。如果有几个库之间有相互声明的关系,则用常用的命令行方式无法解决。对于这种情况,可以使用这里讲到的按组包含方式。在--start-group与--end-group之间的库文件会被做为一个组来使用,链接器会反复搜索这一组库文件,直到找到所有的符号(前提当然是能够找到,呵呵)。
由于组内的库文件会被反复搜索,链接速度会大手影响,因此链接器手册上建议用户只有在库文件之间存在相互声明,且这种声明不可避免时才使用分组方式。
在我们的实际工程中也由使用分组方式的情况,那就是最后链接所有库文件生成可执行文件时。由于库文件很可能存在相互声明,因此分组方式也不可避免。
6 参考文献
GCC Complete Reference Arthur Griffith
Using the GNU Compiler Collection (GCC) Richard M. Stallman and the GCC Developer Community
The GNU linker Steve Chamberlain, Ian Lance Taylor