免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1340 | 回复: 0
打印 上一主题 下一主题

GNULinux应用程序编程——第四章:GNU编译器工具集 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-07-30 01:57 |只看该作者 |倒序浏览
翻译自——GNULinux应用程序编程(GNU/Linux Application Programming) by M.Tim Jones
第四章:GNU编译器工具集
概览
编译过程的再次回顾
GCC一般模式的介绍
使用GCC优化程序
使用GCC警告选项
GCC构架上的详细说明
相关工具:size和objdump
介绍
  GNU编译器集合(另外一种叫法——GCC)是一个编译器和一套程序的组合,可以把高级源代码编译成二进制文件。GCC不仅是GNU/Linux上事实上的标准编译器,同时它还是嵌入式系统开发上的标准编译器。这是因为GCC支持很多不同的目标构架。例如我们的使用在这里将集中于基于主机的开发(为平台构建软件并在该平台编译),但如果我们已经完成了编译(为了一个不同的目标而构建)另外GCC提供40个不同的构架家族。还有X86,RS6000,Arm,PowerPC…等等。GCC还可以在40多个不同的主机系统(比如:Linux, Solaris, Windows… 等等)上使用。
  GCC还支持一系列语言除了标准C以外,我们还可以编译C++,Ada,Java,Objective-C,FORTRAN,Pascal,还有三个C语言的变种(dialect方言)。
  在这一章中,我们将看一看GCC的基本特点和一些高级特点(包括最优化)。我们还将看一看在图形构建上十分有用的在GCC内的相关工具(比如:size,objcopy…等等)。
  注意:我们在这章中将介绍3.2.2版的GCC。它是Red Hat9.0上的默认版本。新的版本已经出现,但是在这里所讨论的细节依然兼容。
编译的介绍
  在GNU编译器构建一个对象的过程中需要好几个阶段。这些阶段可以被滤分为4个:预处理,编译,汇编,链接。
预处理,编译和汇编在一般情况下是三个合成一个阶段,但是在图中为了说明GCC的功能我们把它们分开了。表格4.1确认了输入文件和输出文件。
表格4.1
阶段
输入
输出
GCC例子
预处理
*.c
*.i
gcc –E test.c –o test.i
编译
*.i
*.s
gcc –S test.i –o test.s
汇编
*.s
*.o
gcc -c test.s -o test.o
链接
*.o
*
gcc test.o –o test
在预处理阶段,先处理源文件(*.c)的包含文件(.h头文件)。在这一阶段诸如#ifdef, #include和#define等指令将被解决。产生的结果是一个中间文件(*.i)。一般来说,在外表上看这个文件是不完全的,但是我们将在这里将完全的文件和过程展示出来。既然源文件已经做过了预处理,那么经过编译阶段后它将被编译为汇编格式的文件(*.s)。然后在汇编阶段汇编格式的文件将会转换为机器命令变成目标文件(*.o)。最后机器代码将会链接(一般来说是和其它的机器代码或者库目标文件相链接)在一起转换为一个可执行的二进制文件。
  对于初学者来说这已经足够了,现在让我们更深入地去探索GCC,看一看对GCC的各种不同的用法。我们将首先看一看能够说明GCC的使用的一系列模式,然后我们将探讨一些GCC的最有用的选项(包括:调试,各种警告设置和最优化)。最后我们将研究和GCC相关的一些GNU工具。
GCC模式(编译,编译和链接)
  开始的最简单的例子是将C源代码文件编译为一个镜像。在这个例子当中整个必须的源代码都包含在了一个单一的文件之中,所以我们像下面那样来使用GCC:
$ GCC test.c -o test
  在这里我们编译了test.c文件并将产生的可执行镜像放置于一个叫做text的文件当中(使用了-o Output输出选项)。如果我们只是需要源代码的目标文件的话我们可以使用标记-c,比如:
$ GCC -c test.c
  默认地,产生的目标文件被命名为text.o,但是我们可以强制地命名输出的目标文件为newtest.o,比如
$ GCC -c test.c -o newtest.o
  很多我们将要开发的程序将会包含不止一个文件。GCC可以在命令行上简单地处理这样的情况,比如:
$ GCC -o image first.c second.c third.c
  在这里我们编译了三个源文件(first.c second.c third.c)并将它们一起链接到一个叫做image的可执行文件当中。
  注意:在所有的将会产生一个可执行文件的例子中,所有的C程序需要一个叫做main的函数-它是一个程序的主人口点并在所有被编译以及链接的文件当中之出现一次。当简单地编译一个源文件为一个目标文件时,将不会执行链接故而main函数并非必要。
有用的选项
  在很多情况中,我们将我们的头文件保存到一个与我们的源代码保存的地方相分开的目录当中。考虑一下这么一个例子:我们的代码保存在一个叫做./src的子目录当中,而另一个子目录./inc则保存着我们的头文件。当我们在目录./src当中进行编译的时候,我们可以告诉GCC头文件的所在,比如:
$ gcc test.c -I../inc -o test
  我们可以通过多个-I选项来指定多个包含头文件的目录:
$ gcc test.c -I../inc -I../../inc2 -o test
  这里我们又指定了一个包含头文件的目录../../inc2。
  为了配置软件我们可以在命令行上指定符号恒量。例如,在一个源文件或者头文件当中定义一个符号恒量:
#define TEST_CONFIGURATION
  它可以简单地在命令行上通过选项-D的使用来定义:
$ gcc -DTEST_CONFIGURATION test.c -o test
  在命令行上指定这个恒量的好处是:我们并不需要修改源代码来改变它的行为(由符号恒量所指定的)。
  最后一个有用的选项提供了我们一个可以显示源代码和汇编散布的列表,例如:
$ gcc -c -g -Wa,-ahl,-L test.c
  在这个命令当中最为让人感兴趣的是选项-Wa-它将接下来的选项传递到汇编阶段来通过汇编去散布C源文件。
编译器警告
  尽管编译器会在发现错误的时候终止编译的进程(发现的警告暗示应解决潜在的问题),但是它依然可以产生一个有效的可执行文件。GCC提供了一个十分丰富的警告系统,但是它必须可以获得可探测的全系列的警告的好处。
  GCC的最常用来寻找一般警告的是选项-Wall,它打开了一个指定类型的所有警告,包含了所有在应用程序当中最为常见的问题。它的使用是这样的:
$ gcc -Wall test.c -o test
选项
目的
unused-function
对一个没有定义但是已经声明了的静态函数的警告
unused-label
对一个已经声明了的但是没有还使用的标志的警告
unused-parameter
对一个还没有使用的函数参数的警告
unused-variable
对一个还没有使用的局部声明的变量的警告
unused-value
对一个已经计算但是还没有使用的值的警告
format
逐个核对printf的格式化字符串是否拥有基于在格式化字符串当中所指定的类型的合法的参数
impcitli-int
当一个声明没有指定一个类型的时候发出警告
implicit-function-
对一个在其声明之前使用的函数的警告
char-subscripts
如果一个数组被一个字符下标标注(common error considering that the type is signed)则发出警告
missing-braces
如果一个聚合体初始化程序没有被方括号括起则发出警告
parentheses
如果()的缺少导致含义模糊的话发出警告
return-type
如果函数声明默认为int类型或者需要返回的函数缺少一个返回则发出警告
sequence-point
对比如像a = c[i++];之类的可疑的代码元素发出警告
switch
在一个缺少默认语句的switch语句中对缺少可能为switch的参数的情况警告
strict-aliasing
使用了最为严格的变量的别名规则(比如试图将一个void*别名为double
unknown-pragmas
对一个不认识的编译指示指令进行警告
uninitialized
对那些已经使用了的但是还没有初始化的变量进行警告(enabled only with -O2 optimization level)
  -Wall的一个同义词为-all-warnings。表格4.2列出了过多的包含在-Wall中的选项。
  注意:大多数选项都拥有一个相反的选项格式使得它们可以失效(如果在默认的情况之下它是开着的抑或被一个集合选项所包含)。例如,如果我们想要打开-Wall但是要是某些不使用的选项集失效,我们可以像下面那样来指定:
$ gcc -Wall -Wno-unused test.c -o test
选项
目的
cast-align
无论什么时候当一个指针被重铸类型并所需的校正被增加的时候发出警告
sign-compare
当一个有符号变量和一个无符号变量进行比较时可能产生一个错误的结果时发出警告
missing-prototypes
对一个没有在先前进行原型定义的并已经被使用了的全局函数发出警告
packed
如果一个结构提供了packed属性并且没有然后packing产生则发出警告
padded
如果一个数组被填充来校正它(产生一个相当大的结构)则发出警告
unreachable-code
如果代码被发现不能够被执行则发出警告
inline
当一个被标志为内联函数的函数不能够成为内联函数则发出警告
disabled-optimization
当一个优化器不能够执行所指定的优化时(需要太多的时间或者资源来执行)发出警告
很多其它的选项可以在选项-Wall之外被打开。表格4.3提供了一些更为有用的选项及其描述的一个列表。
  最后一个非常有用的警告选项为-Werror。这个选项指定:与其简单地发现一个问题警告一个,还不如让编译器将所有警告看作为错误并终止编译进程——这在保证高质量代码上十分有用,因此我们推荐这种做法。
GCC优化器
  优化器的工作本质上来说是要承担三个潜在的互不相关的任务中的一个。它可以将代码优化得运行得更快或者体积更小——运行得更快的话体积一般来说就会更大,而减少代码的体积大小的话将会使得它运行得更慢。幸运的是我们可以让优化器去完成我们想要的结果。
注意:当GCC优化器进行代码优化的时候,有时它会产生更大或更小的镜像(与我们的追求所相反)。因此测试你获得的镜像是否和你所希望的一致变得尤为重要。当结果和你所希望的不一致的时候,改变你向前向优化器提供的选项通常会有所帮助。
在这一部分,我们将会介绍用GCC进行代码优化的各个不同机制。
表格4.4 优化设置及其描述
优化级别
描述
-o0
不优化(默认级别)
-o , -o1
减少编译的时间以及镜像的大小
-o2
比-o1更进一步的优化(只对那些don’t increase over speed (或者相反的)适用)
-os
使产生的镜像大小更为优化(除了那些增加大小的所有 –o2)
-o3
更多的优化(-o2,再加两三个)
在它的最简单的形式当中,GCC提供了一系列可以被激活的优化级别。选项-o(oh)允许指定5个不同的列举的表格4.4当中的优化级别
优化器的激活只需简单地在GCC命令行上指定优化级别。例如,在下面的命令行当中,我们指示优化器去减少产生的镜像的大小:
$ gcc –os test.c –o test
注意——为每一个将要组成一个镜像的文件指定不同的优化级别是可能的。还有一些没有包括在优化级别之内的需要把所有文件都以同一优化选项编译(一旦一个文件使用这个选项,那么所有文件都要使用它)的选项存在。我们不会在这里介绍它们中的任何一个。
现在让我们更进一步地深入到优化级别当中去,来看一看它们各自做什么同时鉴别提供的单一优化。
-o0 优化
当指定了-o0优化选项(或者根本没有任何优化器说明指定)时,编译器将仅仅产生提供了所希望的结果并可以简单地被一个源代码调试器(比如GNU Debugger,gdb)调试的代码。编译当不优化的时候是十分快捷的,这是因为优化器根本没有被调用。
-o1 优化(-o)
在优化的第一个级别当中,优化器的目标是尽可能快地进行编译同时减少产生的代码以及执行时间。与-o1级别相比,用-o0级别进行编译需要消耗更多的时间同时这还取决于被编译的源代码,而这通常是不可见。
表格:4.5在-o1当中可用的优化
优化级别
描述
defer-pop
推迟从堆栈当中弹出函数参数直到必要的时候
thread-jumps
执行跳转(jump)穿线(threading)优化(避免跳转到跳转)
branch-probabilities
用分支(branch) profiling(模型)来优化分支
cprop-registers
执行一个寄存器复制传播优化遍数
guess-branch-probability
激活分支可能性推测
Omit-frame-pointer
不要产生栈帧(尽可能地)
你可以在表格4.5当中看到在-o1中的单一优化选项。
如果你依旧需要安全地调试产生的镜像那么-o1优化选项通常为一个安全的级别。
注意:当分别地指定优化选项的时候,选项-f被用来识别它们。例如,要激活defer-pop优化选项,我们只需要简单地像这样来定义—— -fdefer-pop。如果选项是通过一个优化级别来激活并且你需要它被关闭,你可以简单地使用它的否定形式—— -fno-defer-pop。
-o2 优化
优化的第二级别提供了更多的优化选项(同时包括了在-o1中的那些优化),但是它不包含那些为了空间而牺牲速度的优化(反之亦然)。在-o2当中出现的优化选项已在表格4.6当中列出。
注意表格4.6只是列出了那些对于-o2来说是唯一的优化选项。而-o2应该被看做为表格4.5以及4.6的集合。
-os优化
-os级别的优化只是简单地禁止了-o2当中的一些选项,否则的话这些选项将会导致产生的镜像的体积变大。这些被禁止的出现在-o2当中的选项为:-falign-labels, -falign-jumps, -falign-labels以及-falign-functions。它们当中的每一个都极有可能导致产生的镜像的体积变大,因此禁止它们有助于构建应该体积更小的可执行文件。
表格4.6:在-o2当中可用的优化选项
优化选项
描述
align-loops
对齐循环的起点
align-jumps
对齐那些只有跳转可获取的标签
align-labels
对齐所有标签
align-functions
对齐函数的起点
optimize-sibling-calls
优化sibling(兄弟)以及尾部(tail)的递归调用
ces-follow-jumps
当执行CSE时,跟随跳转转移到跳转的目标
ces-skip-blocks
当执行CSE时,跟随条件跳转转移
gcse
执行公共的全局子表达式的消除
expensive-optimizations
执行一套开销大的优化
strength-reduce
执行减少强度的优化
rerun-cse-after-loop
在循环优化之后返回CSE
caller-saves
激活寄存器来保存around函数调用
force-mem
在使用之前拷贝操作数内存到寄存器当中
peephole2
在sched2之前激活一个rtl peephole
regmove
激活寄存器迁移优化选项
strict-aliasing
假定严格的别名使用规则应用
delete-null-pointer-checks
删除无用的空指针检查
reorder-blocks
记录基本的块来改善代码的布局
schedule-insns
在寄存器分配之前重新调度指令
schedule-insns2
在寄存器分配之后重新调度指令
rerun-loop-opt
再运行循环优化器两次
-o3优化
-o3级别的优化是GCC提供的最高级别的优化。除了那些提供在-o2当中的优化选项,这个级别的优化选项还包括列于表格4.7当中的选项。
表格4.7:激活在-o3当中的优化选项(在-o2之上)
优化选项
描述
-finline-functions
在调用函数当中嵌入一些简单的函数
-frename-registers
为带有大量寄存器(这使得调试十分可能)的架构优化寄存器分配
构架优化
标准优化级别可以对软件的性能和代码大小提供有意义的改善,同时指定目标架构也十分有用。选项-mcpu告诉编译器为指定的CPU类型产生指令。对于标准X86目标,表格4.8提供了一些选项。
表格4.8:支持X86的CPU架构
目标CPU
-mcpu=
i386 DX/SX/CX/EX/SO
i386
i486 DX/SX/CX/EX/SO
i486
487
i486
Pentium
pentium
Pentium MMX
Pentium-mmx
Pentium Pro
pentiumpro
Pentium ||
pentium2
Cleleron
pentium2
Pentium |||
pentium3
Pentium |V
pentium4
Via C3
c3
Winchip2
winchip2
Winchip C6-2
winchip-c6
AMD K5
i586
AMD K6
K6
AMD K6 ||
K6-2
AMD Athlon
athlon
AMD Athlon4
athlon
AMD Athlon XP/MP
athlon
AMD Duron
athlon
AMD Tbird
athlon-tbird
AMD K6 |||
K6-3
所以如果我们正在为Intel Celeron架构编译的话,我们可以使用下面的命令:
$ gcc  –mcpu=pentium2  test.c  –o  text
当然,将-mcpu选项和一个优化级别合并将会获得一个额外的性能的提升。一个十分重要的值得注意的一点是:一旦我们为一个指定的CPU进行编译 ,得到的结果将不能在其它CPU上运行。因此,如果我们希望一个镜像能够在大多数CPU上运行的话,让编译器选择默认的选项(i386)将会使得镜像能够被所有的X86架构所支持。
调试选项
如果我们希望使用一个符号调试器来调试我们的代码,我们可以通过指定选项–g来产生调试信息(in the image for GDB)。选项–g可以指定一个能够指定输出的信息的格式的参数。要要求调试信息以dwarf-2的格式,我们可以使用如下命令:
$ gcc  -gdwarf-2  test.c –o test
其它工具
在本章的最后一部分,我们将来看一看在开发过程当中能够给予我们帮助的其它GNU工具(通常被称为binutils)。
首先,我们怎么知道我们的可执行镜像或者我们的中间目标文件有多大?工具程序size能够显示指定文件(可执行文件或者中间目标文件)的文本段的大小(指令的数目)、数据段以及bss段的大小。让我们来看一下下面的命令:
$ size  test.o
text  data  bss  dec  hex  filename
789  256  4  1049 419  test.o
$
在这里我们要求显示我们的中间目标文件test.o的大小。我们可以看到:文本段(指令和常量)的大小是789字节,数据段的大小是256字节然后bss段(它将会自动化初始化为0)的大小是4字节。如果我们想要看到更多的有关镜像的详细的信息,我们可以使用工具程序objdump。通过使用参数-syms我们可以来探索目标文件镜像的符号表。
$ objdump  –syms  test.o
它将会显示例如类型(文本段,bss,数据段)、长度、偏移量等等在目标文件之内的一连串符号。同时我们还可以使用-disassemble参数来分解镜像:
# objdump  -dissassemble  test.o
这个命令将会为我们显示目标文件内的连同由GCC一个一个编译产生的指令在内的一系列函数。
最后,工具程序nm同样可以用来了解在目标文件内的符号。这个程序不仅仅显示每一个符号,同时它还将显示符号的类型的详细的信息。在nm的手册页上你还可以看到众多的有用的选项。
总结
在这一章当中,我们讨论研究了GCC编译器和一些相关的工具。我们研究了GCC的一些常用模式以及警告选项的详细的用法。我们还回顾了由GCC提供的不同的优化级别以及提供了更大优化的架构指定。。最后,我们回顾了几个和GCC产品相关的工具,比如size、objdump以及nm。





本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/6957/showart_349671.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP