文章目录

  • 通过本文的学习,了解gcc创建静态库、动态库的详细过程。
  • 前言
  • 一、浅谈静态库和动态库
  • 1、什么是静态库
  • 2、什么是动态库
  • 3、二者的区别
  • 二、用 gcc 生成 .a 静态库和 .so 动态库
  • 1、编辑生成例子程序 hello.h、hello.c 和 main.c
  • 2、将 hello.c 编译成.o 文件
  • 3、由.o文件创建静态库
  • 4、在程序中使用静态库
  • 5、由.o文件创建动态库文件
  • 6、在程序中使用动态库
  • 7、拓展:当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?
  • 三、实例演训
  • 1、作业要求
  • 2、实操过程
  • 2.1、编辑主程序文件和子程序文件
  • 2.2、将.c文件编译成.o文件
  • 2.3、将目标文件用 ar工具生成 .a 静态库文件, 再用 gcc进行链接,生成可执行程序
  • 2.4、将目标文件用 ar工具生成 .so 动态库文件, 再用 gcc进行链接,生成可执行程序
  • 2.5、静态库与动态库文件大小比较
  • 总结



前言

我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库;动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
本文主要通过实例来说明:在Ubuntu18.04系统下如何创建静态库和动态库,以及使用方法。


一、浅谈静态库和动态库

1、什么是静态库

可重定位目标文件以一种特定的方式打包成一个单独的文件,并且在链接生成可执行文件时,从这个单独的文件中“拷贝”它自己需要的内容到最终的可执行文件中。这个单独的文件,称为静态库。linux中通常以.a(archive)为后缀

用下面这个例子来说,我们使用静态链接构建我们的可执行文件:

$ gcc -c main.c
$ gcc -static -o main main.o -lm

在上述过程中,就会用到系统中的静态库libm.a
那么上述过程完成了哪些步骤呢?首先第一条命令会将main.c编译成可重定位目标文件main.o,第二条命令的static参数,告诉链接器应该使用静态链接,-lm参数表明链接libm.a这个库(类似的,如果要链接libxxx.a,使用-lxxx即可)。

特别注意,必须把-lm放在后面。放在最后时它是这样的一个解析过程:

  • 链接器从左往右扫描可重定位目标文件和静态库
  • 扫描main.o时,发现一个未解析的符号exp,记住这个未解析的符号
  • 扫描libm.a,找到了前面未解析的符号,因此提取相关代码
  • 最终没有任何未解析的符号,编译链接完成

那如果将-lm放在前面,又是怎样的情况呢?大家可以下来思考一下

2、什么是动态库

动态库和静态库类似,但是它并不在链接时将需要的二进制代码都“拷贝”到可执行文件中,而是仅仅“拷贝”一些重定位和符号表信息,这些信息可以在程序运行时完成真正的链接过程。linux中通常以.so(shared object)作为后缀。

通常我们编译的程序默认就是实用动态链接:

$ gcc -o main main.c -lm #默认使用的是动态链接

另外我们还可以通过ldd命令来观察可执行文件链接了哪些动态库:

$ ldd main
linux-vdso.so.1 => (0x00007ffc7b5a2000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fe9642bf000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe963ef5000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe9645c8000)

正因为我们并没有把libm.so中的二进制代码“拷贝”可执行文件中,我们的程序在其他没有上面的动态库时,将无法正常运行。

3、二者的区别

区别

说明

可执行文件大小:

静态链接的可执行文件要比动态链接的可执行文件要大得多,因为它将需要用到的代码从二进制文件中“拷贝”了一份,而动态库仅仅是复制了一些重定位和符号表信息

扩展性与兼容性:

如果静态库中某个函数的实现变了,那么可执行文件必须重新编译,而对于动态链接生成的可执行文件,只需要更新动态库本身即可,不需要重新编译可执行文件

依赖性:

静态链接的可执行文件不需要依赖其他的内容即可运行,而动态链接的可执行文件必须依赖动态库的存在

加载速度:

静态库在链接时就和可执行文件在一块了,而动态库在加载或者运行时才链接,因此,对于同样的程序,静态链接的要比动态链接加载更快

二、用 gcc 生成 .a 静态库和 .so 动态库

1、编辑生成例子程序 hello.h、hello.c 和 main.c

  • 先创建一个作业目录,保存练习的文件:

#mkdir test1
#cd test1

meson动态编译_ubuntu

  • 用 vim、nano 或 gedit 等文本编辑器编辑生成所需要的 3 个文件:
    (这里以vim编辑器为例,分别编译 hello.h、 hello.c、main.c)
    具体代码如下:
/*程序 1: hello.h*/
    #ifndef HELLO_H
    #define HELLO_H
    void hello(const char *name);
    #endif //HELLO_H
/*程序2:hello.c*/
    #include <stdio.h>
    void hello(const char *name)
     {
       printf("Hello %s!\n", name);
     }
/*程序3:main.c*/
    #include "hello.h"
    int main()
     {
       hello("everyone");
       return 0;
     }

注:hello.c是函数库的源程序,其中包含公用函数 hello,该函数将在屏幕上输出"HelloXXX!"。hello.h为该函数库的头文件。main.c为测试库文件的主程序,
在主程序中调用了公用函数 hello。)

  • 进入文件后,键入“i”,即可进入编辑模式;代码写进之后,键入“:wq!”,再回车即可保存并退出文件,同时,可以运用“ls”命令查看文件是否创建成功。
    实操效果截图:

2、将 hello.c 编译成.o 文件

  • 无论静态库,还是动态库,都是由.o 文件创建的。因此,必须将源程序 hello.c 通过 gcc 先编译成.o 文件

在系统提示符下键入下列命令得到 hello.o 文件:
gcc -c hello.c

meson动态编译_静态库_02


由上图结果可知,在 ls 命令结果中,生成了 hello.o 文件

3、由.o文件创建静态库

  • 静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为 myhello,则静态库文件名就是 libmyhello.a
  • 创建静态库用 ar 命令

在系统提示符下键入如下命令,创建静态库文件libmyhello.a:
ar -crv libmyhello.a hello.o

meson动态编译_ubuntu_03


由上图结果可知,在 ls 命令结果中,生成了 libmyhello.a文件

4、在程序中使用静态库

  • 如何使用静态库内部的函数呢?
    只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后用gcc 命令生成目标文件时指明静态库名,gcc 将会从静态库中将公用函数连接到目标文件中。注意,gcc 会在静态库名前加上前缀 lib,然后追加扩展名.a 得到的静态库文件名来查找静态库文件。
  • 在程序 3:main.c 中,我们包含了静态库的头文件 hello.h,然后在主程序 main 中直接调用公用函数 hello。下面先生成目标程序 hello,然后运行 hello 程序看看结果
  • 方法一:

gcc -o hello main.c -L. –lmyhello

:自定义的库时,main.c 还可放在-L.–lmyhello 之间,但是不能放在它俩之后,否则会提示 myhello 没定义但是是系统的库时,如g++ -o main(-L/usr/lib) -lpthread main.cpp就不出错。)

  • 方法二:

gcc main.c libmyhello.a -o hello

  • 方法三:

先生成 main.o:
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a

:动态库连接时也可以这样做)

  • 实操结果展示(以方法一为例):
  • meson动态编译_静态库_04

  • 可以看到,输入命令运行后,键入./hello,返回Hello everyone!
  • 删除静态库文件,确定公用函数hello是否真的连接到目标文件hello中:

rm libmyhello.a
./hello

meson动态编译_linux_05


由上图可以看出,命令运行之后,键入“ls”命令,发现静态库文件libmyhello.a已经被删除,但键入“./hello”后,仍会输出“Hello everyone!”;这说明:程序照常运行,静态库中的公用函数已经连接到目标文件中了

5、由.o文件创建动态库文件

  • 动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib,但其文件扩展名为.so。例如:我们将创建的动态库名为 myhello,则动态库文件名就是 libmyhello.so
  • 用 gcc 来创建动态库

键入如下命令得到动态库文件libmyhello.so:
gcc -shared -fPIC -o libmyhello.so hello.o (-o 不可少)

meson动态编译_ubuntu_06


由上图结果可知,在 ls 命令结果中,生成了 libmyhello.so文件

6、在程序中使用动态库

  • 在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用 gcc 命令生成目标文件时指明动态库名进行编译。
  • 先运行 gcc 命令生成目标文件,再运行它看看结果:

gcc -o hello main.c -L. -lmyhello
或 gcc main.c libmyhello.so -o hello (没有 libmyhello.so 的话,会出错)
./hello

meson动态编译_meson动态编译_07


通过上述结果可以发现,第一行命令没有问题,但是当我们键入“./hello”时,却出现了错误提示,由错误提示可知,虽然连接时用的是当前目录的动态库,但程序在运行时,会在/usr/lib/lib 等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行

  • 将文件 libmyhello.so 复制到目录/usr/lib 中,再试试:

mv libmyhello.so /usr/lib
./hello

meson动态编译_c语言_08


有图可以看到,当输入第一行命令时,出现报错,无法进行文件的复制,提示为“Permission denied”,即权限不够

  • 解决办法:

键入“sudo su”,进入root身份,再键入“su root”,最后输入相关命令:
sudo su
su root
mv libmyhello.so /usr/lib
./hello

meson动态编译_静态库_09

终于成功!

这也进一步说明了:动态库在程序运行时是需要的

7、拓展:当静态库和动态库同名时,gcc 命令会使用哪个库文件呢?

  • 先删除除.c 和.h 外的所有文件,恢复成刚刚编辑完举例程序状态:
  • 并运用“ls”命令进行查看:

rm -f hello hello.o /usr/lib/libmyhello.so
ls

meson动态编译_linux_10

  • 再来创建静态库文件 libmyhello.a 和动态库文件 libmyhello.so:

gcc -c hello.c
ar -cr libmyhello.a hello.o
gcc -shared -fPIC -o libmyhello.so hello.o

meson动态编译_c语言_11


通过上述最后一条 ls 命令,可以发现静态库文件 libmyhello.a 和动态库文件 libmyhello.so 都已经生成,并都在当前目录中

  • 运行 gcc 命令来使用函数库 myhello 生成目标文件 hello,并运行程序 hello:

gcc -o hello main.c -L. -lmyhello
./hello

meson动态编译_linux_12


由图可知,结果出现了报错。从该运行的结果中很容易知道,当静态库和动态库同名时,gcc 命令将优先使用动态库,默认去连/usr/lib 和/lib 等目录中的动态库,将文件 libmyhello.so 复制到目录/usr/lib中即可。

补充:动态库和静态库同时存在时,优先使用动态库,当然,如果直接

#gcc main.c libmyhello.a -o hello 的话,就是指定为静态库了)

三、实例演训

1、作业要求

  • 在第一次作业的程序代码基础进行改编,除了x2x函数之外,再扩展写一个x2y函数(功能自定),main函数代码将调用x2x和x2y ;将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件
  • 将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小
  • 将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比

2、实操过程

2.1、编辑主程序文件和子程序文件

  • 创建一个目录,用来保存此次练习的文件:

mkdir test2
cd test2

  • 利用vim命令编辑文件sub1.c,sub2.c,sub.h,main.c,代码如下:
//main.c
#include <stdio.h>
#include"sub.h"
   int main()
     {
        int a = 9, b = 6;
        float c;
        float d;
        c = x2x(a, b);
        d = x2y(a, b);
        printf("%f\r\n", c);
        printf("%f\r\n", d);
    	return 0;
     }
//sub1.c
    float x2x(int a, int b)
      {
	     float c;
       	 c = a * b;
	     return c;
      }
//sub2.c
float x2y(int a,int b)
{
	float d;
	d = a + b;
	return d;
}
//sub.h
#ifndef SUB_H
#define SUB_H
float x2x(int a,int b);
float x2y(int a,int b);
#endif

2.2、将.c文件编译成.o文件

键入如下命令将三个.c文件编译成.o文件:
gcc -c sub1.c
gcc -c sub2.c
gcc -c main.c
ls

meson动态编译_c语言_13

2.3、将目标文件用 ar工具生成 .a 静态库文件, 再用 gcc进行链接,生成可执行程序

  • 将两个目标文件用ar工具生成.a静态库:

ar -crv libsub.a sub1.o sub2.o

meson动态编译_linux_14

  • 用 gcc进行链接,生成可执行程序,并运行之:

gcc -o sub main.c -L. -lsub
./sub

meson动态编译_meson动态编译_15


至此,目标文件与静态库的链接,以及可执行程序的生成与执行全部完成。

2.4、将目标文件用 ar工具生成 .so 动态库文件, 再用 gcc进行链接,生成可执行程序

  • 将两个目标文件用ar工具生成.so动态库,用 gcc进行链接:

gcc -shared -fPIC -o libsub.so sub1.o sub2.o
gcc -o sub main.c -L. -lsub
./sub

meson动态编译_meson动态编译_16


又出现了与之前一样的情况,因此,再次进入root模式,重新进行操作

  • 解决办法:

键入“sudo su”,进入root身份,再键入“su root”,最后输入相关命令:
sudo su
su root
gcc -shared -fPIC -o libsub.so sub1.o sub2.o
gcc -o sub main.c -L. -lsub
./sub

meson动态编译_c语言_17


至此,目标文件与动态库的链接,以及可执行程序的生成与执行全部完成。

2.5、静态库与动态库文件大小比较

  • 键入ll用来查看静态库与动态库文件大小

    由图可知,静态库生成文件比动态库要小得多