GCC工作过程以及动态库静态库链接
转自 https://www.tianmaying.com/tutorial/GCC
1. GCC介绍
GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器。它是以GPL许可证所发行的自由软件,也是 GNU计划的关键部分。GCC原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,GCC同样适用于微软的Windows。[2] GCC是自由软件过程发展中的著名例子,由自由软件基金会以GPL协议发布。GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言。GCC 很快地扩展,变得可处理 C++。后来又扩展能够支持更多编程语言,如Fortran、Pascal、Objective-C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。
2. GCC编译过程介绍
GCC的编译流程分为4个步骤:
-
预处理(Preprocess)
-
编译(Compilation)
-
汇编(Assembly)
-
链接(Linking)
下面分别介绍这几个过程在干什么,并用一个简单的HelloWorld的例子进行说明,源代码如下所示:
#include<stdio.h>
int main(){
printf(“Hello World\n”);
return 0;
}
2.1 预处理(Preprocess)
预编译过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:
- 将所有的#define删除,并且展开所有的宏定义;处理所有条件编译指令,如#if,#ifdef等;
- 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件;
- 删除所有的注释//和 /**/;
- 添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
-
保留所有的#pragma编译器指令,因为编译器须要使用它们;
在GCC中,我们执行指令
gcc –E test.c –o test.i
得到hello.i,结果生成预处理之后的test.i文件,如下图所示:
2.2 编译(Compilation)
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。在这里GCC内真正起作用的是C编译器ccl,执行命令为:
gcc –S test.i –o test.s
我们得到这样一个汇编代码文件test.s,如下图所示:
2.3 汇编(Assembly)
汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。输入命令运行即可得到test.o的目标文件
gcc –c test.c –o test.o
这里实际起作用的是GNU汇编器as。
2.4 链接(Linking)
链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件.在这个源程序中并没有定义printf的函数实现,且在预编译中包含进的"stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现printf函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数printf了,而这也就是链接的作用。函数库一般分为静态库和动态库两种,第三部分我来介绍函数库的部分。执行下面命令即可得到可执行文件test
3. 动态库与静态库
函数库一般分为静态库和动态库两种。
- 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。
- 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。(Linux下动态库文件的扩展名为".so"(Shared Object)。按照约定,所有动态库文件名的形式是libname.so(可能在名字中加入版本号)。这样,线程函数库被称作 libthread.so。静态库的文件名形式是libname.a。共享archive的文件名形式是libname.sa。共享archive只是一种过渡形式,帮助人们从静态库转变到动态库。)
先构造一个小的例子,用来说明如何用GCC编译得到动态库和静态库
hello1.c文件为:
void print1(int i) { int j;
for(j=0;j<i;j++) { printf("%d * %d = %d\n",j,j,j*j); } }
hello2.c文件为:
void print2(char *arr) { char c; int i=0; while((c=arr[i++])!='\0') { printf("%d****%c\n",i,c); } }
hello.c文件为:
void print1(int);
void print2(char *);
int main(int argc,char **argv) {
int i=100;
char *arr="THIS IS LAYMU'S HOME!";
print1(i);
print2(arr);
return 0;
}
hello.c使用到了hello1.c和hello2.c中的函数,可以把这两个函数组合为库,以供更多的程序作为组件来调用。下面采用编译为静态库和编译为动态库的方式分别进行说明。
3.1 静态库使用
- 将hello1.c和hello2.c分别编译为hello1.o和hello2.o
gcc -c hello1.c hello2.c
- 将hello1.o和hello2.o组合为libhello.a这个静态链接库
ar -r libhello.a hello1.o hello2.o
- 将libhello.a拷贝到/usr/lib目录下,作为一个系统共享的静态链接库
cp libhello.a /usr/lib
- 将hello.c编译为可执行程序hello,这个过程用到了-lhello选项,这个选项告诉gcc编译器到/usr/lib目录下去找libhello.a的静态链接库
gcc -o hello hello.c -lhello
执行生成的可执行程序hello,即可得到输出结果。开一下小的脑洞,既然hello中包含了libhello.a这个静态链接库文件的内容,如果删除libhello.a之后,那么hello依然也可以执行,结果的确是这个样子
3.2 动态库使用
- 将hello1.c和hello2.c编译成hello1.o和hello2.o,-c意为只编译不链接,-fpic意为位置独立代码,指示编译程序生成的代码要适合共享库的内容这样的代码能够根据载入内存的位置计算内部地址。实际上只是告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,因为现在还无法知道使用该消息代码的应用程序会将它链接到哪一段内存地址空间。
gcc -c -fpic hello1.c hello2.c
- 将hello1.o和hello2.o组合为shared object,即动态链接库
gcc -shared hello1.o hello2.o -o hello.so
- 将hello.so拷贝到/usr/lib目录下
cp hello.so /usr/lib
- 将hello.c编译链接为hello的可执行程序,这个过程用到了动态链接库hello.so
gcc -o hello hello.c hello.so
可以通过lld命令查看生成的hello程序以来的共享库包括那些,如下图所示,可以看到对hello.so的依赖
同样我们删掉hello.so,那么就无法执行,如下图所示:
3.3 静态库与动态库链接、执行时的搜索路径顺序
3.3.1 静态库链接时搜索路径顺序
- ld会去找GCC命令行中的参数-L的目录中是否有该静态库;
- 再去找GCC的环境变量LIBRARY_PATH
- 再找内定目录/lib、/usr/lib、/usr/local/lib夏是否有该链接库,这是当初compile gcc的时候确定的
3.3.2 动态库链接时、执行时搜索路径顺序
- 编译目标代码时指定的动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
- 默认的动态搜索路径/lib;
- 默认的动态库搜索路径/usr/lib
大家可以去找找看相应的环境变量和conf文件
3.4 静态库与动态库的优缺点分析
静态链接库的一个缺点是,如果我们同时运行了很多程序,并且使用了同一个库函数,这样在内存中就会大量拷贝同一个库函数,这样会浪费很多内存和存储空间。
当一个程序使用动态库函数时,在链接阶段并不把函数代码链接进来,而只是链接函数的一个引用,当最终的函数倒入内存开始真正运行时,函数引用被解析,共享函数库的代码才真正被倒入到内存中,这样共享链接库的函数就可以被许多程序同时共享,而且只存储一次即可。同时动态库可以独立更新,与调用它的程序互不影响。
3.5 如何使用库
gcc中关于库的参数有:
-L 指定搜寻库的目录
如指定当前目录 gcc -L .
-l 指定要链接的库的名称
加入库的名称是libmylib.a,则gcc -l mylib,即去头去尾。
--static 组织在链接时使用动态库
--shared 生成动态库
--static-libgcc 链接静态libgcc库
--shared-libgcc 链接动态libgcc库





浙公网安备 33010602011771号