嵌入式Linux C语言开发工具—编译器gcc详解

时间:2021-08-21 12:33:52

      在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux 程序员面临的首要问题都是如何灵活运用C编译器。目前Linux 下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、 Fortran、Pascal、Modula-3和Ada等。开放、*和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。

     在Linux平台上,最流行的编译系统是GCC(GNU Compile Collection)。GCC也是GNU发布的最著名的软件之一。GCC的功能非常强大,主要体现在两方面。

1) GCC可以为x86、ARM、MIPS等不同体系结构的硬件平台编译程序。

2) GCC可以编译C、C++、Pascal、Java等数十种高级语言。

     GCC的这两项特性对嵌入式应用开发及其重要。此外,GCC的编译效率也是非常高的,一般要高出其他编译系统20%到30%左右。所以在嵌入式Linux开发领域,使用的基本上就是GCC编译系统。

gcc 常用选项及工作流程

    

gcc命令的使用格式为:

  gcc [选项] [文件名] [选项] [文件名]

gcc命令拥有数量庞大的编译选项,按类型可以把选项分为以下几大类。

总体选项:用于控制编译的整个流程。

常用选项:

-c:对源文件进行编译或汇编。

-E:对源文件进行预处理。

-S:对源文件进行编译。

-o file:输出目标文件file。

-v:显示编译阶段的命令。

语言选项:用于支持各种版本的C语言程序。

常用选项:

-ansi:支持符合ANSI标准的C程序。

警告选项:用于控制编译过程中产生的各种警告信息。

常用选项:

-W:屏蔽所有的警告信息。

-Wall:显示所有类型的警告信息。

-Werror:出现任何警告信息就停止编译。

调试选项:用于控制调试信息。

常用选项:

-g:产生调试信息。

优化选项:用于对目标文件进行优化。

常用选项:

-O1:对目标文件的性能进行优化。

-O2:在-O1的基础上进一步优化,提高目标文件的运行性能。

-O3:在-O2的基础上进一步优化,支持函数集成优化。

-O0:不进行优化。

连接器选项:用于控制链接过程。

常用选项:

-static:使用静态链接。

-llibrary:链接library函数库文件。

-L dir:指定连接器的搜索目录dir。

-shared:生成共享文件。

目录选项:用于指定编译器的文件搜索目录。

常用选项:

-Idir:指定头文件的搜索目录dir。

-Ldir:指定搜索目录dir。

此外,还有配置选项等其他选项,这里不做介绍了。

    编译系统本身是一种相当复杂的程序,编写甚至读懂这样的程序都是非常困难的。但是从事嵌入式Linux应用的开发人员都应掌握编译系统的基本原理和工作流程。

 

    开放、*和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。在使用GCC编译程序时,编译过程可以被细分为四个阶段:

预处理(Pre-Processing)

 编译(Compiling)

 汇编(Assembling)

 链接(Linking)

     Linux程序员可以根据自己的需要让 GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。 

GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。

 

在学习使用GCC之前,下面的这个例子能够帮助用户迅速理解GCC的工作原理,并将其立即运用到实际的项目开发中去。首先用熟悉的编辑器输入清单1所示的代码:

 

1 #include <stdio.h> 

2 int main() 

3 { 

4 printf("Hello world!\n"); 

5 return 0; 

6 }

     由于在test.c中使用了头文件stdio.h,所以GCC在编译时首先要把头文件stdio.h中的内容加载到test.c中的首部。

在shell中输入命令“gcc -E test.c -o test.i”。其中,参数E告诉gcc命令只进行预编译,不做其他处理;参数o用来指明输出的文件名为test.i。命令运行完毕后就会产生一个名为test.i的文件。如下所示:

[root@localhost home]#gcc -E test.c -o test.i

[root@localhost home]#ls

test.c test.i

test.i文件的代码有一百多行,如下所示的是test.i文件的最后部分代码。

extern char *ctermid (char *__s) __attribute__ ((__nothrow__));

# 820 "/usr/include/stdio.h" 3 4

extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__));

extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__)) ;

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));

# 850 "/usr/include/stdio.h" 3 4

# 2 "test.c" 2

int main(){ printf(“Hello world!\n”); return 0;}

 

2. 编译阶段

     编译阶段是整个编译过程中最复杂的一个阶段。这里拿自然语言的翻译过程作个对比。比如在把“I love China”翻译成中文前,需要依次完成以下几个步骤:

1)   考察这个句子中每个单词的拼写是不是正确。

2)   考察整个句子的语法(比如主谓宾、定状补的结构等)是不是正确。

3)   考察整个句子的语义是不是正确。

只有以上三个步骤都正常通过了,才能保证句子被正确翻译。同样,高级编程语言的编译阶段也必须实现这三个步骤。

1)   步骤1称为词法分析,主要负责检查关键字、标识符等是否正确。

2)   步骤2称为语法分析,主要负责检查程序中语句的语法是否正确。

3)   步骤3称为语义分析,主要负责检查程序中语句的逻辑意义是否正确。

    在shell中输入命令“gcc -S test.i -o test.s”。其中,参数S告诉gcc命令只进行编译,不做其他处理。命令运行完毕后就会产生一个名为test.s的汇编文件。如下所示:

[root@localhost home]#gcc -S test.i -o test.s
[root@localhost home]#ls
test.c test.i test.s
    在学习使用汇编语言编程的时候,对照C文件和其汇编程序是很好的办法。如下所示的是test.s的代码。
  
.file"test.c"
.section.rodata
.LC0:
.string"Hello world!"
.text
.globl main
.typemain, @function
main:
leal4(%esp), %ecx
andl$-16, %esp
pushl-4(%ecx)
pushl%ebp
movl%esp, %ebp
pushl%ecx
subl$4, %esp
movl$.LC0, (%esp)
callputs
movl$0, %eax
addl$4, %esp
popl%ecx
popl%ebp
leal-4(%ecx), %esp
ret
.sizemain, .-main
.ident"GCC: (GNU) 4.2.1"
.section.note.GNU-stack,"",@progbits

  

    注意,以上所示的汇编代码是针对x86平台的。

3. 汇编阶段

    汇编阶段的任务是把汇编程序翻译成CPU可以识别的二进制文件,该文件又称为目标文件。

在shell中输入命令“gcc -c test.s -o test.o”,其中,参数c告诉gcc命令只进行汇编,不做其他处理。命令运行完毕后就会产生一个名为test.o的目标文件。如下所示:

   

[root@localhost home]#gcc -c test.s -o test.o

[root@localhost home]#ls

test.c test.i test.o test.s

 

在Windows系统中,目标文件的后缀是obj。

4. 链接阶段

    目标文件虽然已经可以被CPU直接识别,但是单个目标文件一般是无法运行的。原因在于一个程序往往是由多个源文件组成的,每个源文件只对应一个目标文件。也许有人会问,test程序不就只有一个源文件test.c吗,为什么也不能直接运行呢?原因是test.c使用了stdio.h对应的库函数,所以必须要把test.o文件和函数库文件链接在一起才能运行。

    链接阶段的任务就是把程序中所有的目标文件和所需的库文件都链接在一起,最终生成一个可以直接运行的文件,称为可执行文件。

    在shell中输入命令“gcc test.o -o test”,运行完毕后就会产生一个名为test的可执行文件。输入命令“./test”执行该文件,就可以得到test文件的运行结果“Hello world!”。 如下所示:

[root@localhost home]#gcc test.o -o test

[root@localhost home]#./test

Hello world!

    gcc命令生成的可执行文件的有以下三种格式。

1)a.out(Assembler and Link editor output);

2)COFF(Common object file format);

3)ELF(Executable and linkable format);

    其中,a.out和COFF格式都是比较老的格式,现在Linux平台上可执行文件的主流格式是ELF。