嵌入式Linux开发工具之gcc编译器

2019-07-12 19:54发布

   

gcc的简介


编译器gcc所具备的优点


1、GNU CC(简称为gcc)是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。gcc不仅功能强大,而且可以编译如C、C++、Object C、Java、和Ada等多种语言,而且gcc又是一个交叉平台编译器,它能够在当前CPU平台上为多种不同体系结构的硬件平台开发软件,因此尤其适合在嵌入式领域的开发编译. 2、那么什么是交叉编译呢? 
交叉编译就是:在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码编译过程。 
简单地说,就是在一个平台上生成另一个平台上的可执行代码。比如说:我们的宿主机pc机是x86体系结构的处理机,我们的目标机手机是arm体系结构的处理机,然后我们在pc机上编译了一段可以在手机上执行的代码,这个过程就是交叉编译。
这里需要注意的是所谓平台,实际上包含两个概念:体系结构(Architecture)、操作系统(Operating System)。同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。举例来说,我们常说的x86 Linux平台实际上是Intel x86体系结构和Linux for x86操作系统的统称;而x86 WinNT平台实际上是Intel x86体系结构和Windows NT for x86操作系统的简称。


gcc-编译选项


编译器编译的过程



一个.c文件转换成一个可执行文件,实际上需要经过以下四步: 
预处理—–>编译—–>汇编—–>链接
一、预处理过程 
1、预处理过程,主要负责头文件展开、宏替换、条件编译等工作。gcc -E表示让编译器只进行至预处理步骤。经过预处理的.c文件变成.i文件。 
eg: 
gcc -E hello.c 让编译器只进行到预处理步骤,结果输出到终端。 
gcc -E hello.c > hello.i 对预处理步骤进行重定向,输出到.i文件中。
2、头文件的写法 
/usr/include/ :头文件存放在根目录下的usr目录下的include目录中。
头文件有两种写法,一种是<>包含的头文件,一种是”“包含的头文件。 
例如: 
#include  
#include "stdio.h" 
两者区别如下: 
<>包含的头文件查找时,先去系统的头文件目录中找,如果找不到就会报错。 
“”包含的头文件查找时,先在当前的头文件目录中找,如果找不到,再去系统的头文件目录中找,还找不到就报错。 
PS: 
一般情况下,系统自带的头文件用<>包含;开发人员自己写的头文件用”“包含。
二、编译过程 
编译过程,负责将预处理生成的文件,经过词法分析,语法检查生成目标文件。gcc –c表示只进行至编译阶段。.o文件为目标文件。 
eg: 
gcc -c hello.c 只进行至编译阶段
三、汇编过程 
汇编过程,是将目标文件转换为汇编文件的过程。通过使用gcc –S命令进行至汇编阶段。.s文件为汇编文件。 
eg: 
gcc -S hello.c 只进行至汇编阶段,生成hello.s汇编文件
四、链接过程 
链接,负载根据目标文件及所需的库文件产生最终的可执行文件。 
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够在操作系统装入执行的统一整体。 
链接主要解决了模块间的相互引用的问题,分为地址和空间分配,符号解析和重定位几个步骤。实际上在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的。链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。
//怎么查看程序运行耗费时间:                                             time ./a.out gcc编译器的三级优化:                                     -o1                                     优化,该模式下对于一个大的函数或功能会花费更多的时间和内存。编译会尝试减少代码体积和代码运行时间,但是并不执行大量时间的优化操作;                                     -o2                                      进一步优化。Gcc执行几乎所有支持的操作但不包括空间和速度之间权衡的优化。在-o2优化等级下,并不执行循环展开和函数“内联”优化操作;                                    -o3                                      更进一步优化,-o3打开-o2的所有优化操作        

静态库与动态库


  一、库文件 
库文件就是把一些函数与变量封装在库里,我们能够使用和调用这些函数与变量的功能,但是却看不到其实现过程的文件。 
你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数,但是不能看到函数的实现过程,其文件也不能单独运行!这是库文件的一大特点。
Linux的库文件分为两类: 
1、静态库:是在执行程序之前就已经加入到执行代码中,成为执行程序的一部分来执行的,后缀名:.a; 
2、动态库:是在执行程序启动时加载到执行代码中,后缀名:..so 。 
那么其两者的区别在于:
  • 静态库:执行程序之前加载到执行代码中,导致编译速度慢,但是执行效率高;导致可执行文件比较臃肿,体积比较大,占用更多内存;静态库不易升级,升级需重新编译;静态库代码易布局,布局也会影响执行效率。
  • 动态库:在执行程序启动时加载到执行代码中,导致编译速度快,但是执行效率低;可执行文件体积比较小,占用内存比较少;动态库易升级,更新库即可;动态库代码不易布局,影响执行效率,减慢。
总而言之,相较之下,静态库的执行效率较高。 /lib-系统库 
/usr/lib-程序库
二、静态库的制作&使用 
创建: 
gcc -c 源文件.c 
ar rcs 静态库名 目标文件1 目标文件2
库的命名必须加前缀lib,后缀.a eg: 
gcc -c add.c 
ar rcs libadd.a add.o 
gcc main.c -ladd 
gcc main.c -ladd -L. -o main 
./main 
sum=11

三、动态库的制作 
创建: 
gcc -shared -fPIC -o 库名.so 源文件.c
eg: 
gcc -shared -fPIC -o libadd1.so add.c 
gcc main.c ./libadd1.so -o add 
./add 
sum=11 四、相关编译选项 
-shared:表示产生共享对象,产生的代码会在装载时进行重定位。但是无法做到让一份指令由多个进程共享。因为单纯的装载时重定位会对程序中所有指令和数据中的绝对地址进行修改。要做到让多个进程共享,还需要加上-fPIC。
-fPIC:地址无关代码,是为了能让多个进程共享一份指令。基本思想就是将指令中需要进行修改的那部分分离出来,跟数据放到一块。这样指令部分就可以保持不变,而需要变化的那部分则与数据一块,每个进程都有自己的一份副本。 
参考资料