Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4470615
  • 博文数量: 1148
  • 博客积分: 25453
  • 博客等级: 上将
  • 技术积分: 11949
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-06 21:14
文章分类

全部博文(1148)

文章存档

2012年(15)

2011年(1078)

2010年(58)

分类:

2011-06-07 18:58:43

原文地址:GCC(v4.1.2)编译器分析 作者:fireaxe

 

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 

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个字符的字符串,格式为“181034

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      前缀“--

常用的打开编译选项的方法是一个连字符加上一个字母。现在出现了另一种加选项的方法,两个连字符加上编译选项。许多选项具有以上两种形式,但做的是相同的事。例如:-gdebug其实是一种编译选项的两种写法。

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++语言)。常见的标准如下:

c89ISO C89标准

iso9899:1990ISO C90 ( 相当于是 “-ansi”)

c99ISO C99标准

gnu89:默认配置,ISO C90GNU扩展(包含部分C99特性,如在C中使用注释符“\\”)

gnu99ISO C99GNU扩展标准。随着GCCISO 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 ”添加的头文件不会在dir中搜索。

3.1.9  -Dmacro[=string]

定义一个预定义宏,如果没有“[=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-identifiers

 

3.2.2   

 

 

3.3      前缀“-m

m代表machine,这一类命令代表与架构有关的编译选项(Machine-Specific Compiler Options)。

此类选项的大部分都是与特殊的平台(specific platform)有关,都是用来为在某一类平台上所具有的特性产生特殊的代码结构。但也由一选项用于调试目的,或者用来组织目标文件(object files)的段结构。例如,选项“-msdata”用于在目标文件中生成小数据段(small data segment

注:对于这些与特殊平台有关的选项,本文不会涉及太多,只是对一些常用的命令做些说明。

3.3.1  mcpu=8313

 

3.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-probability

 

4.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-jumps

 

4.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”。

常用的段有bssdatatext三种段,因此有如下三种命令形式(注意:第二种格式在段前有一个“.”):

 

简单格式

复杂格式

代码段

-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      -defsym

 

5.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

 

本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。
作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 
阅读(1109) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

qizheguang2012-03-31 10:42:17

学习一下