编译器GCC

GCC编译器

GCC(GNU Compiler Collection)是一套功能强大、性能优越的编程语言编译器,它是GNU计划的代表作品之一。GCC是Linux平台下最常用的编译器,GCC原名为GNU C Compiler,即GNU C语言编译器,它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。它的名称也逐渐变成了GNU Compiler Collection。

GCC是GNU项目的编译器组件之一,也是GNU最具有代表性的作品。在GCC设计之初仅仅作为一个C语言的编译器,可是经过十多年的发展,GCC已经不仅仅能支持C语言;它现在还支持Ada语言、C++语言、Java语言、Objective C语言,Pascal语言、COBOL语言,以及支持函数式编程和逻辑编程的Mercury语言等等。而GCC也不再单是GNU C Compiler,而是GNU Compiler Collection也即GNU编译器家族,目前已经成为Linux下最重要的编译工具之一。

GCC的编译流程分为四个步骤:

◆ 预处理(Pre-Processing) cpp

◆ 编译(Compiling)            cc1

◆ 汇编(Assembling)          as

◆ 链接(Linking)                ld

GCC是一个交叉平台的编译器,目前支持几乎所有主流CPU处理器平台,它可以完成从C、C++、Objective C等源文件向运行在特定cpu硬件上的目标代码的转换,GCC不仅功能非常强大,结构也异常灵活,便携性(protable)与跨平台支持(cross-plantform. support)特性是GCC的显著优点。

扩展名 对应语言 编译流程
.c C源程序 预处理、编译、汇编
.C/.cc/.cxx C++源程序 预处理、编译、汇编
.m Objective-C源程序 预处理、编译、汇编
.i 经过预处理的C源程序 编译、汇编
.ii 经过预处理的C++源程序 编译、汇编
.s/.S 汇编语言源程序 汇编
.h 预处理文件(头文件) 预处理
.o 目标文件 链接
.a/.so 静态/动态库文件 链接

 

GCC是一组编译工具的总称,其软件包里包含众多的工具,按其类型,主要有以下的分类。

  C编译器cc,gcc

  C++编译器c++,g++

  源代码预处理程序cpp

  库文件libgcc.a,libgcc_eh.a,libgcc_s.so,libiberty.a,libstdc++.[a,so],libsupc++.a

编译器通过程序的扩展名来识别编写源程序所使用的语言,由于不同程序所需要执行的编译步骤是不同的,因此根据不同的扩展名进行相应的处理。

gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S为后缀的汇编语言源代码文件和汇编、.s为后缀的汇编语言文件经过预编译和汇编之后都生成以.o为后缀的目标文件。当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的档案库中连到合适的地方。

常用编译选项

  • l -c选项:这是GCC命令的常用选项。-c选项告诉GCC仅把源程序编译为目标代码而不做链接工作,所以采用该选项的编译指令不会生成最终的可执行程序,而是生成一个与源程序文件名相同的以.o为后缀的目标文件。例如一个Test.c的源程序经过下面的编译之后会生成一个Test.o文件
  • l -E选项:预处理后即停止,不进行编译、汇编及连接
    l -S选项:使用该选项会生成一个后缀名为.s的汇编语言文件,但是同样不会生成可执行程序。
  • l -e选项:-e选项只对文件进行预处理,预处理的输出结果被送到标准输出(比如显示器)。
  • l -v选项:在Shell的提示符号下键入gcc -v,屏幕上就会显示出目前正在使用的gcc版本的信息
  • l -x language:强制编译器指定的语言编译器来编译某个源程序。
  • l -o选项: 指定输出文件file

 这里有一段简单的C语言程序,该程序由两个文件组成,其中“hello.h”为头文件,在“hello.c”中包含了“hello.h”,其源文件如下所示。

/* hello.h */

#ifndef_HELLO_H_

#define_HELLO_H_

typedef unsigned long val32_t;

#endif

/* hello.c */

#include <stdio.h>

#include <stdlib.h>

#include "hello.h"

int main(int argc, char *argv[])

{

         val32_t I = 5;

         printf("hello,embedded world%d\n", i);

}

A.         预处理阶段

GCC的选项“-E”可以使编译器在预处理结束时就停止编译,选项“-o”是指定GCC输出的结果

#gcc –E –o [目标文件] [编译文件]

后缀名为“.i”的文件是经过预处理的C原始程序。要注意,“hello.h”文件是不能进行编译的,因此,使编译器在预处理后停止的命令

#gcc –E –o hello.i hello.c

在此处,选项‘-o’是指目标文件,而‘.i’文件为已经过预处理的C原始程序。以下列出了hello.i文件的部分内容。

#2"hello.c"2

#1"hello.h"1

typedef unsigned long val32_t;

#3"hello.c"2

int main()

{

val32_t i=5;

printf("hello,embedded world%d\n",i);

}

         由此可见,GCC确实进行了预处理,它把“hello.h”的内容插入到hello.i文件中了。

B.         编译阶段

编译器在预处理结束之后,就进入编译阶段,GCC在编译阶段首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,就开始把代码翻译成汇编语言,GCC的选项“-S”能使编译器在进行完汇编之前就停止。“.s”是汇编语言原始程序,因此,此处的目标文件就可设为“.s”类型。

以下列出了hello.s的内容,可见GCC已经将其转化为汇编了。

#gcc –S –o hello.s hello.i

.file"hello.c"

.section.rodata

.LC0:

.string"hello,embedded world%d\n"

.text

.globl main

.type main,@function

main:

pushl%ebp

movl%esp,%ebp

subl$8,%esp

andl$-16,%esp

movl$0,%eax

addl$15,%eax

addl$15,%eax

shrl$4,%eax

sall$4,%eax

subl%eax,%esp

movl$5,-4(%ebp)

subl$8,%esp

pushl-4(%ebp)

pushl$.LC0

call printf

addl$16,%esp

leave

ret

.size main,.-main

.section.note.GNU-stack,"",@progbits

..ident"GCC:(GNU)4.0.0 20050519(Red Hat 4.0.0-8)"

         这一小段C语言的程序在汇编中已经复杂很多了,这也是C语言作为高级语言的优势所在

C.         汇编阶段

汇编阶段是把编译阶段生成的“.s”文件生成目标文件,使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了。

# gcc –c hello.s –o hello.o

D.         链接阶段

在成功编译之后,就进入了链接阶段。将生成的目标文件与其他目标文件(或库文件)连接成可执行的二进制代码文件

静态链接:将所需要的函数库中的目标代码静态添加到可执行文件中

动态链接:将所需函数库的函数名,路径信息等添加到可执行文件中,执行过程中动态加载所需函数库

在这里涉及一个重要的概念:函数库。

问题:在这个程序中并没有定义“printf”的函数实现,在预编译中包含进的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现“printf”函数的呢?

答案:

系统把这些函数实现都已经被放入名为libc.so.6的库文件中去了,在没有特别指定时,GCC会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。

         完成了链接之后,GCC就可以生成可执行文件

# gcc hello.o –o hello

运行该可执行文件,出现正确的结果

# ./hello

hello,embedded world 5

 

        -static选项:GCC在默认情况下链接的是动态库,有时为了把一些函数静态编译到程序中,而无需链接动态库就采用-static选项,它会强制程序连接静态库
        -g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。
        -O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。
        -O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。
        -Idirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。

C程序的文件包含两种情况∶
        A)#include <stdio.h>
        B)#include “myinc.h”
        其中,A类使用尖括号(<>),B类使用双引号(“ ”)。对于A类,预处理程序cpp在系统预设包含文件目录(如/usr/include)中搜寻相应的文件,而对于B类,cpp在当前目录中搜寻头文件,这个选项的作用是告诉cpp,如果在当前目录中没有找到需要的文件,就到指定的dirname目录中去寻找。在程序设计中,如果我们需要的这种包含文件分别分布在不同的目录中,就需要逐个使用-I选项给出搜索路径。
        -Ldirname,将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在连接过程中使用的参数。在预设状态下,连接程序ld在系统的预设路径中(如/usr/lib)寻找所需要的档案库文件,这个选项告诉连接程序,首先到-L指定的目录中去寻找,然后到系统预设路径中寻找,如果函数库存放在多个目录下,就需要依次使用这个选项,给出相应的存放目录。
        -lname,在连接时,装载名字为“libname.a”的函数库,该函数库位于系统预设的目录或者由-L选项确定的目录下。例如,-lm表示连接名为“libm.a”的数学函数库。

 

生成(函数)库文件:一般可大分为两种函数库文件 静态函数库与动态(共享)函数库

创建静态函数库过程:  gcc -c   example1.c -I(所需头文件所在目录) -o example1.o

                                 gcc -c   example2.c -I(所需头文件所在目录) -o example2.o

                                ar -rv libexample.a  example1.o example2.o (创建静态函数库,将这两个目标文件添加至函数库中)

                                ar -tv libexample.a       (查看函数库中文件)

 

 创建动态函数库过程:  gcc  -fPIC -c   example1.c -I(所需头文件所在目录) -o example1.o 

                                 gcc  - fPIC -c   example2.c -I(所需头文件所在目录) -o example2.o

                             gcc -shared example1.o example2.o -o libexample.so  (创建动态函数库,将这两个目标文件添加至函数库中)

 

静态链接函数库:      gcc   example.c -I(所需头文所在目录) -L(所需函数库所在目录) -lexample(指定所需的函数库名) -o example

动态链接函数库: gcc   -s example.c -I(所需头文所在目录) -L(所需函数库所在目录) -lexample(指定所需的函数库名) -o example

 

注意要指定LD_LIBRARY_PATH至函数库文件所在目录,才能正常使用函数库

 

GCC具有优化代码的功能,主要的优化选项包括如下:

-O0:不进行优化处理。
-O或-O1:进行基本的优化,线程跳转和延迟退栈等。这些优化在大多数情况下都会使程序执行得更快。
-O2:除了完成-O1级别的优化外,还要一些额外的调整工作,如处理器指令调度等,这是GNU发布软件的默认优化级别。
-O3:除了完成-O2级别的优化外,还进行循环的展开以及其他一些与处理器特性相关的优化工作。
-Os:生成最小的可执行文件,主要用于在嵌入式领域。
 

一般来说,优化级别越高,生成可执行文件的运行速度也越快,但消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候才考虑对最终生成的代码进行优化。

-finline-functions:允许编译器将一些简单的函数在其调用处展开。
-funswitch-loops:将循环体中值不改变的变量移到循环体外。

         -g选项:生成调试信息,GNU调试器可以利用该信息。GCC编译器使用该选项进行编译时,将调试信息加入到目标文件中,这样gdb调试器就可以根据这些调试信息来跟中程序的执行状态。

         -pg选项:编译完成后,额外产生一个性能分析所需信息。

注意:使用调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项统常推荐仅仅在程序开发和调试阶段中使用。

 

posted @ 2012-12-26 15:36  雪中飞  阅读(1688)  评论(0编辑  收藏  举报