嵌入式Linux中文站

GNU GCC 编译器简介


GCC编译器是由一系列工具构成,其中包括:预处理器cpp,编译器前端gcc/g++,汇编器as,连接器ld/ar。一个编译过程包括下面几个阶段:

        (1)预处理,预处理器CPP将对源文件中的宏进行展开。

        (2)编译,gcc/g++将c/c++文件编译成汇编文件。

        (3)汇编,as将汇编文件编译成机器码。

        (4)连接,ld将目标文件和外部符号进行连接,得到一个可执行二进制文件。

        GCC编译C源码的四个步骤如下图所示:预处理-----> 编译 ----> 汇编 ----> 链接。

GCC编译器GNU Compiler Collection (GCC) - dapeng - 本鹏的博客

一.  编译过程

         1. 预处理(Pre-processing)

        gcc会首先调用cpp进行预处理:cpp -o test.i test.c 或者 gcc -o test.i -E test.c。 预处理的输出的文件一般以.i作为后缀。该阶段,编译器对伪指令和特殊符号进行处理,其中包括宏、条件编译、包含的头文件以及一些特殊符号。基本上是一个replace的过程。

         2. 编译(Compiling)

         编译阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码 (gcc -o test.s -S test.i。

          3. 汇编阶段(Assembling)
          汇编阶段将汇编语言代码翻译成目标机器指令,把编译阶段生成的“.s”文件转成目标文件".o",使用选项“- c”就可看到汇编代码已转化为“.o”的二进制目标代码(gcc –c test.s –o test.o 或 as -o test.o test.s)。test.o是一个relocatable的ELF文件。

          4. 链接(Linking)

          将在一个文件中引用的符号同在另外一个文件中该符号的定义链接起来(gcc -o test test.o 或者 ld -o test test.o),使得所有的这些目标文件链接成为一个能被操作系统加载到内存的执行体。(如果有不到的符号定义,或者重复定义等,会报链接错)。用file test 可以看到test是一个executable的ELF文件。

        链接的过程中会涉及到函数库的处理。函数库一般分为静态库和动态库两种;静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,其后缀名一般为“.a” 。

        动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。Gcc 在编译时默认使用动态库。

        通过ar可以生成静态链接库ar -v -q test.a test.o。gcc -shared test.so test.o 来创建动态链接库。

 

二. gcc 编译选项分析

gcc 有超过100个的可用选项,主要包括总体选项、告警和出错选项、优化选项和体系结构等相关选项。这里列举几个常用的选项:

       1. 总体选项

       -c 只是编译不链接,生成目标文件“.o”

        -S 只是编译不汇编,生成汇编代码

        -E 只进行预编译,不做其他处理

        -g 在可执行程序中包含标准调试信息

        -o file 把输出文件输出到 file 里

         -v 打印出编译器内部编译各过程的命令行信息和编译器的版本

         -I dir 在头文件的搜索路径列表中添加 dir 目录

         -L dir 在库文件的搜索路径列表中添加 dir 目录

         -static 链接静态库

         -llibrary 连接名为 library 的库文件

       “-I dir”选项可以在头文件的搜索路径列表中添加 dir 目录。由于 Linux中头文件都默认放到了“/usr/include/”目录下,因此,当用户希望添加放置在其他位置的头文件时,就可以通过“-I dir”选项来指定,这样,gcc 就会到相应的位置查找对应的目录。

        注:在 include 语句中, “<>”表示在标准路径中搜索头文件,“” “ ”表示在本目录中搜索。

       选项“-L dir”的功能与“-I dir”类似,能够在库文件的搜索路径列表中添加 dir 目录。需要注意的是, “-I dir”和“-L dir”都只是指定了路径,而没有指定文件,因此不能在路径中包含文件名。另外值得详细解释一下的是 “-l”选项, 它指示 gcc 去连接库文件 libsunq.so。由于在 Linux下的库文件命名时有一个规定:必须以l、i、b 3 个字母开头。因此在用-l 选项指定链接的库文件名时可以省去 l、i、b 3 个字母。也就是说 gcc 在对“-lsunq”进行处理时,会自动去链接名为 libsunq.so 的文件。

       2 告警和出错选项

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

       -pedantic 允许发出 ANSI C 标准所列的全部警告信息

       -pedantic-error 允许发出 ANSI C 标准所列的全部错误信息

       -w 关闭所有告警

        -Wall 允许发出 Gcc 提供的所有有用的报警信息

       -werror 把所有的告警信息转化为错误信息,并在告警发生时终止编译过程

       3 优化选项

        gcc 可以对代码进行优化,它通过编译选项“-On”来控制优化代码的生成,其中 n 是一个代表优化级别的整数。对于不同版本的 Gcc来讲,n 的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从 0 变化到 2 或 3。不同的优化级别对应不同的优化

          处理工作。如使用优化选项“-O”主要进行线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。使用优化选项“-O2”除了完成所有“-O1”级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项“-O3”则还包括循环展开和其他一些与处理器特性相关的优化工作。虽然优化选项可以加速代码的运行速度,但对于调试而言将是一个很大的挑战。因为代码在经过优化之后,原先在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句也有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。所以笔者建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

        4. 体系结构相关选项

        -mcpu=type 针对不同的 CPU 使用相应的 CPU 指令。可选择的 type 有 i386、i486、pentium 及 i686 等

        -mieee-fp 使用 IEEE 标准进行浮点数的比较

        -mno-ieee-fp 不使用 IEEE 标准进行浮点数的比较

          -msoft-float 输出包含浮点库调用的目标代码

         -mshort 把 int 类型作为 16 位处理,相当于 short int

        -mrtd 强行将函数参数个数固定的函数用 ret NUM 返回,节省调用函数的一条指令

本文永久更新链接:http://embeddedlinux.org.cn/emb-linux/entry-level/201109/23-1632.html



分享:

评论