Linux C 库的生成与使用

库文件的存在极大的提高了 C/C++ 程序的复用性,本文简单介绍如何生成、使用它们。

命名约定:
我们一般对库文件约定以 lib 开头,动态库文件一般以 .so(*nix)或 .dll(Windows) 结尾,静态库文件一般以 .a(*nix)或 .lib(Windows) 结尾。

动态库还分为两种用法:

  1. 在编译期间声明动态库的存在,运行时由系统链接器自动链接
  2. 在运行期间动态加载和卸载,由用户程序决定何时链接

下面我们以 Linux 为例,Windows 的用法与此有所不同,但思路大同小异。

生成和使用静态链接库

将库源码编译成目标文件:

$ gcc -c struct.c [-o struct.o]

打包库文件:

$ ar cr libstruct.a struct.o # 注意参数顺序不能错

这里的 artar 类似,是一种打包工具。

可以使用 $ ar -t libstruct.a 命令 检查包含哪些 .o 文件。

构建符号表:

$ ranlib libstruct.a

这个命令不是必须的,是因为部分 ar 的实现集成了该功能,此时的 ranlib 就是一个空壳命令。

链接静态库,生成可执行文件:

$ gcc main.c -static -L . -lstruct -o main

因为我们约定库文件以 lib 开头,所以这里可以省掉它。

设置库文件的环境路径:

  1. 定义 LD_LIBRARY_PATH 环境变量,添加动态库文件的路径。
  2. 把库路径添加到 ld.so.conf 文件中,然后用 ldconfig 加载。
  3. 使用 ldconfig 临时加载。

编译时,如果出现重名的动态库和静态库,优先使用动态库。

生成和使用动态链接库

编译动态链接库文件:

$ gcc -shared -fPIC struct.c -o libstruct.so

这其实融合了好几条命令,但我们不做细述。

-fPIC 表示编译为“地址无关”的代码,用于编译共享库。如果不加 -fPIC 则加载 so 文件的代码段时,代码段引用的数据对象需要重定位,重定位会修改代码段的内容,这就造成每个使用这个 so 文件代码段的进程在内核里都会生成这个 so 文件代码段的副本。

一般总是用 -fPIC 来生成 .so 文件,也从来不用fPIC来生成 .a 文件。

一般以下几种情况不需要:

  1. 该库可能需要经常更新
  2. 该库需要非常高的效率,尤其是有很多全局量的使用时
  3. 该库并不大
  4. 该库基本不需要被多个应用程序共享

链接动态库,生成可执行文件:

$ gcc main.c -L . lstruct -o main

一般我们习惯将动态链接库放入系统库目录:

$ gcc -shared -Wl,-soname,libstruct.so.1 -o libstruct.so.1 *.o

其中 -Wl 前缀的命令会传递给 ld 命令。我们之前说过,gcc 融合了多条命令。

也就是在链接 .o 文件时会执行:

$ ld -soname libstruct.so.1

libstruct.so 用于在编译期间使用 -lstruct 找到动态库,而 libstruct.so.1 则在运行期间真正被链接。

$ mv libstruct.so.1 /opt/lib
$ ln -sf /opt/lib/libstruct.so.1 /opt/lib/libstruct.so.1
$ ln -sf /opt/lib/libstruct.so.1 /opt/lib/libstruct.so

查看程序对动态库的依赖

$ ldd main

libstruct.so.1 => /opt/lib/libstruct.so.1 (0x00002aaaaaaac000)
libc.so.6 => /lib64/tls/libc.so.6 (0x0000003aa4e00000)
/lib64/ld-linux-x86-64.so.2 (0x0000003aa4c00000)

ldd 脚本中文详解

查看 obj 文件的符号表

obj 文件的格式和组成可能是系统差异性的一大体现,比如 Windows 下的 PE、Linux 和部分 Unix 下的 elf、Mac OS 下的 mach-o、aix 下的 xcoff。

使用 nmobjdumpreadelf 等命令可以查看 .o .a .so 文件中的符号信息。

比如,下面这将打印出 hello.o 中未定义的符号:

$ nm -u hello.o

在运行时动态加载和卸载库

需要应用程序希望设计成插件化的架构,这就需要可以动态加载和卸载库的机制。与动态链接不同的是,动态加载的意思是,编译期间可以对动态库的存在一无所知,而是在运行期间通过用户程序尝试加载进来的。

通过 dlfcn.h 中的 dlopendlsymdlclose 等函数实现此种功能。

另外,使用 dlfcn机制需要在链接时使用 -rdynamic 选项,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被 static 修饰的函数)都添加到动态符号表(即 .dynsym 表)里。

GNU Libtool

如今许多软件的编译都采用 libtool 工具,libtool 是一个编译链接包装工具,实际只是一个脚本,用 libtool 编译和链接会产生类似 .la 的文件,这种文件其实是个文本文件,指向 .a 文件,并声明一些版本信息。

更多

如果想更好地了解,需要多看 gcc / ar / ldd / nm 的参数。

你可能感兴趣的:(Linux C 库的生成与使用)