GCC背后的故事——利用GCC创建静态库与动态库
一、GCC命令的基本语法及应用
1.1 简介
GCC的全名为GNU C COMPILER,作为在Linux环境下的一个重要的编译输出指令。现在经过多年的发展,GCC是GNU Compiler Collection的缩写,是一套由GNU计划开发的自由软件编译器集合。它是一个用于编译程序源代码的工具集,支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada和其他一些语言。GCC是一个非常强大且广泛使用的编译器套件,常用于开发各种不同类型的软件,包括操作系统、应用程序和嵌入式系统等。GCC具有优秀的跨平台能力,可以在各种操作系统上运行,如Linux、Windows和macOS等。该编译器集合提供了丰富的优化选项,可以生成高效和可优化的机器代码。由于GCC是自由软件,用户可以自由地使用、修改和分发它。
本文将主要介绍GCC的常用命令,以及如何使用GCC创建静态库与动态库。
1.2 常用简单编译过程
在之前所发表的博客当中,曾提到过GCC的基本用法,如下所示
接下来我们逐个进行整个编译过程的分析
首先利用Linux创建目录GCC1,在该目录下创建实例程序test.c
利用已创建好的c程序进行预处理,采用语法 gcc -E test.c
。可以输出 test.i 文件中存放着 test.c 经预处理之后的代码。 打开 test.i 文件, 看一看, 就明白了。 后面那条指令, 是直接在命令行窗口中输出预处理后的代码.gcc 的-E 选项, 可以让编译器在预处理后停止, 并输出预处理结果。 在本例中, 预处理结果就是将stdio.h 文件中的内容插入到 test.c 中了。
在预处理后,可直接对test.i文件进行编译生成汇编代码。gcc 的-S 选项, 表示在程序编译期间, 在生成汇编代码后 停止, -o 输出汇编代码文件。
gcc -S test.i -o test.s
接下来对生成的汇编代码进行汇编过程,gas 汇编器负责将其编译为目标文件, 如下:
gcc -c test.s -o test.o
最后,gcc 连接器是 gas 提供的, 负责将程序的目标文件与所需的所有附加的目标文件连接起来, 最终生成可执行文件。 我们利用test.o与C标准输入输出库进行连接,生成最终的可执行文件。gcc test.o -o test
整个编译过程如下:
实际上,在实际运用过程中往往不需要这么多的步骤,GCC已经给出直接语法一步到位
gcc test.c -o test
1.3 多个程序编译
在Linux实际的应用中,通常整个程序是由多个源文件组成的, 相应地也就形成了多个编译单元, 使用 GCC 能够很好地管理这些编译单元。我们在原有的test.c上进行改动,创建新的test1.h头文件用于连接两个文件,并编写新的文件test1.c。
程序撰写完成后,直接使用语法gcc test1.c test.c -o test
,一步到位生成执行文件。
由此可见,如果同时处理的文件不止一个, GCC 仍然会按照预处理、 编译和链接的过程依次进行。 如果深究起来, 上面这条命令大致相当于依次执行如下三条命令:
gcc -c test1.c -o test1.o` `gcc -c test2.c -o test2.o` `gcc test1.o test2.o -o test
二、利用GCC创建静态库和动态库
2.1 简要介绍
我们通常把一些公用函数制作成函数库, 供其它程序使用。函数库分为静态库和动态库两种。
静态库在程序编译时会被连接到目标代码中, 程序运行时将不再需要该静态库。 动态库在程
序编译时并不会被连接到目标代码中, 而是在程序运行是才被载入, 因此在程序运行时还需
要动态库存在。 接下来我们在Linux环境中利用GCC创建.a静态库与.so动态库
2.2 创建静态库与动态库过程
(1)撰写源程序
首先创建一个目录test1,以存储所要用到的程序。
mkdir test1
cd test
1
随后在目录下使用vim创建hello.h,hello.c,main.c三个子程序,如下
通过程序可以得知,hello.c中的“hello”函数通过头文件"hello.h"被主函数进行调用,最后输出的程序结果应该是"Hello!everyone"。我们将分别通过静态库和动态库的方式进行输出,并进行横向对比。
(2)编译文件
通过前面对GCC的基本指令的学习,任何的c程序都需要先转换成.o程序才能进行接下来的编译,在这里,无论静态库, 还是动态库, 都是由.o 文件创建的。 因此, 我们必须将源程序 hello.c 通过 GCC 先编译成.o 文件。 在系统提示符下键入以下命令得到 hello.o 文件。
我们利用gcc -c hello.c
生成.o文件
结果如下
(3)创建并使用静态库
在静态库语法中,静态库文件名的命名规范以lib为前缀,然后跟后缀名,它的后缀名为.a。在这里我们创建libmyhello.a静态库文件,利用ar语法如下
ar -crv libmyhello.a hello.o
查看结果如下:
静态库制作完了, 只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明, 然后在用 gcc 命令生成目标文件时指明静态库名, gcc 将会从静态库中将公用函数连接到目标文件中。 注意, gcc 会在静态库名前加上前缀 lib, 然后追加扩展名.a 得到的静态库文件名来查找静态库文件。在程序 3:main.c 中, 我们包含了静态库的头文件 hello.h, 然后在主程序 main 中直接调用公用函数 hello。
利用GCC生成hello执行文件
gcc main.c libmyhello.a -o hello
然后./hello执行该文件,得出结果如下:
接下来我们尝试删除静态库文件,判断函数hello是否已经连接到执行文件中。
可以看到,删除静态库文件后,程序依然可以输出,可见静态库已成功连接在目标文件中。
(4)创建并使用动态库
接下来我们创建动态库文件,将之前的静态文件,执行文件删除。
动态库文件名命名规范和静态库文件名命名规范类似, 也是在动态库名增加前缀 lib, 但其
文件扩展名为.so。 例如: 我们将创建的动态库名为 myhello, 则动态库文件名就是 libmyh
ello.so。 用 gcc 来创建动态库。
gcc -shared -fPIC -o libmyhello.so hello.o
创建成功后,在程序中使用动态库,一样使用跟静态库类似的语句进行。
gcc main.c libmyhello.so -o hello
发现报错了,意思是说没有在共享资源库中找到该文件。在动态库文件的使用中,系统默认会在/usr/lib 和/lib 等目录中查找需要的动态库文件,因此我们需要将我们所创建的动态库文件拷贝到共享库里。我们将文件 libmyhello.so 移动到目录/usr/lib 中。
成功输出了结果。这也说明了动态库是需要在程序运行的的时候被调用。
在实际的应用中,动态库若与静态库同名,则在gcc使用时优先调用动态库。我们接下来验证这一结果。
我们先删除除.c 和.h 外的所有文件, 恢复成我们刚刚编辑完举例程序状态。
再同时创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so。
然后, 我们运行 gcc 命令来使用函数库 myhello 生成目标文件 hello, 并运行程序 hello。
gcc -o hello main.c -L. –lmyhello
发现报错,说明在同名时,gcc首先使用的是动态库文件,默认去连/usr/lib 和/lib 等目录中的动态库 。
三、比较静态库与动态库的占用大小
学习了静态库和动态库的建立过程,我们接下来比较它们所占用的空间大小
我们在第一次作业中的代码补充一个新的子函数x2y,编写程序如下:
还是首先生成两个.o文件,sub.o与subb.o。随后利用已知语法建立静态库
已经成功建立静态库文件,接着使用它生成执行文件。
利用size语法 和ls -lh语法可以查看执行文件的占用大小。我们接下来建立动态库
通过比较发现静态库要比动态库要小很多,生成的可执行文件大小也存在较小的差别。
四、总结
通过本次实验我们可以得到结论:静态库是在程序编译时进行使用,而动态库则是在程序运行的时候才会被使用。静态库的占用大小要比动态库小,两者各有各的优势,共同运用于我们平时的编译过程。在本次实验,我再一次复习了GCC相关命令,同时对生成静态库,动态库的方式以及他们之间的区别有了非常详细的了解。虽然实验过程中出现较多问题,但都得到很好的解决,让我记忆犹新的就是一定要先生成.o文件再进行后续操作。