Linux 文件系统:动静态库

目录

一、静态库

1、概念 

2、为什么要打包成库

3、创建自定义静态库

4、使用Makefile创建静态库

5、使用静态库(导入系统)

6、删除静态库

 7、使用静态库(不导入系统)

二、动态库

1、概念

2、生成动态库(共享库)

3、使用动态库的过程

4、Makefile同时创建动静态库

5、使用动态库

ldd命令

动静态库同时存在优先使用动态库

-static静态链接 

6、解决无法链接动态库

设置 LD_LIBRARY_PATH 环境变量

添加配置文件

建立软链接


一、静态库

1、概念 

静态库(.a 文件)是在程序编译和链接阶段被整合到最终可执行文件中的代码库。一旦编译完成,程序运行时便不再需要这些静态库文件。这种方式意味着每个使用了静态库的程序都会包含一份库的拷贝,导致可执行文件体积增大。

2、为什么要打包成库

在这个例子中,手动编译了三个源文件(main.cmymath.cmyprint.c),并将它们分别编译成了对象文件(.o 文件),然后再将这些对象文件链接到一个可执行文件中。

虽然这种方法对于一个简单的程序来说可能没什么问题,但当项目变得更加复杂时,手动管理和编译多个源文件会变得非常繁琐和容易出错。

[hbr@VM-16-9-centos uselib]$ gcc -c mymath.c -o mymath.o
[hbr@VM-16-9-centos uselib]$ gcc -c myprint.c -o myprint.o
[hbr@VM-16-9-centos uselib]$ ll
total 20
-rw-rw-r-- 1 hbr hbr  157 Mar 19 22:58 main.c
-rw-rw-r-- 1 hbr hbr   73 Mar 19 23:04 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 19 23:04 mymath.o
-rw-rw-r-- 1 hbr hbr   87 Mar 19 23:04 myprint.h
-rw-rw-r-- 1 hbr hbr 1576 Mar 19 23:04 myprint.o
[hbr@VM-16-9-centos uselib]$ gcc -c main.c -o main.o
[hbr@VM-16-9-centos uselib]$ gcc main.o mymath.o myprint.o -o my.exe
[hbr@VM-16-9-centos uselib]$ ./my.exe 
hello world[1710860798]
res:5050

3、创建自定义静态库

ar命令是一个用于创建、修改以及提取静态库文件的工具,在Unix及类Unix系统中广泛使用。静态库是一种将多个对象文件(.o文件)打包为单个文件(.a文件)的方式,这在编译大型程序时非常有用,因为它允许将多个预编译的代码模块组合成一个单一的库文件。

ar -rc libhello.a mymath.o myprint.o
  • -r 选项表示将文件插入到库中,如果库中已存在同名文件,则替换原文件;
  • -c 选项表示创建库文件,如果库文件不存在则创建新的库文件;
  • libhello.a 是要创建的静态库文件名,lib和.a分别是前缀和后缀,编译时需要的静态库名字是hello。
  • mymath.o myprint.o 是要打包到库中的对象文件列表。

使用ar命令管理静态库是C/C++编程中常见的实践,它允许开发者将代码模块化,便于管理和复用。在后续的使用中,当需要链接这个静态库来编译其他程序时,可以在gcc命令中使用-lhello选项(注意不包括前缀lib和后缀.a,假设静态库文件libhello.a位于编译器的标准库搜索路径中,或者使用-L选项指定库文件的搜索路径。这样,编译器就可以链接这个库,使得库中定义的函数可以在其他程序中被调用。

 通过 ll 命令查看当前目录下的文件列表,可以看到已经创建了 libhello.a 静态库文件。

[hbr@VM-16-9-centos mklib]$ ll
total 24
-rw-rw-r-- 1 hbr hbr  146 Mar 19 22:53 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 19 22:52 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 19 23:02 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 19 22:43 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 19 22:43 myprint.h
-rw-rw-r-- 1 hbr hbr 1576 Mar 19 23:02 myprint.o
[hbr@VM-16-9-centos mklib]$ ar -rc libhello.a mymath.o myprint.o
[hbr@VM-16-9-centos mklib]$ ll
total 28
-rw-rw-r-- 1 hbr hbr 3066 Mar 19 23:15 libhello.a
-rw-rw-r-- 1 hbr hbr  146 Mar 19 22:53 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 19 22:52 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 19 23:02 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 19 22:43 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 19 22:43 myprint.h
-rw-rw-r-- 1 hbr hbr 1576 Mar 19 23:02 myprint.o

4、使用Makefile创建静态库

[hbr@VM-16-9-centos mklib]$ cat makefile 
libhello.a:mymath.o myprint.o
        ar -rc libhello.a mymath.o myprint.o
mymath.o:mymath.c
        gcc -c mymath.c -o mymath.o
myprint.o:myprint.c
        gcc -c myprint.c -o myprint.o

.PHONY:hello
hello:
        mkdir -p hello/lib
        mkdir -p hello/include
        cp -rf *.h hello/include
        cp -rf *.a hello/lib
.PHONY:clean
clean:
        rm -f *.o libhello.a hello

目标和依赖

  • libhello.a: 这是一个静态库目标,它依赖于mymath.omyprint.o两个目标。当这两个目标的任何一个被更新时,make会重新构建libhello.a
  • mymath.omyprint.o: 这两个目标分别依赖于mymath.cmyprint.c源文件。如果相关的.c文件被修改,make会重新编译它们生成.o目标文件。

命令

  • ar -rc libhello.a mymath.o myprint.o: 使用ar工具创建一个名为libhello.a的静态库,包含mymath.omyprint.o
  • gcc -c mymath.c -o mymath.ogcc -c myprint.c -o myprint.o: 使用gcc编译器分别编译mymath.cmyprint.c,生成目标文件。

特殊目标

  • .PHONY: 表示helloclean是伪目标,它们不代表文件名。
  • hello: 创建目录hello/libhello/include,并将所有.h头文件和.a静态库文件复制到相应的目录。
  • clean: 删除所有生成的.o文件、静态库文件和hello目录。

我们运行一下Makefile。

[hbr@VM-16-9-centos mklib]$ ll
total 20
-rw-rw-r-- 1 hbr hbr 327 Mar 19 23:29 makefile
-rw-rw-r-- 1 hbr hbr 146 Mar 19 22:53 mymath.c
-rw-rw-r-- 1 hbr hbr  73 Mar 19 22:52 mymath.h
-rw-rw-r-- 1 hbr hbr  97 Mar 19 22:43 myprint.c
-rw-rw-r-- 1 hbr hbr  87 Mar 19 22:43 myprint.h
[hbr@VM-16-9-centos mklib]$ make
gcc -c mymath.c -o mymath.o
gcc -c myprint.c -o myprint.o
ar -rc libhello.a mymath.o myprint.o
[hbr@VM-16-9-centos mklib]$ ll
total 32
-rw-rw-r-- 1 hbr hbr 3066 Mar 19 23:30 libhello.a
-rw-rw-r-- 1 hbr hbr  327 Mar 19 23:29 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 19 22:53 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 19 22:52 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 19 23:30 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 19 22:43 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 19 22:43 myprint.h
-rw-rw-r-- 1 hbr hbr 1576 Mar 19 23:30 myprint.o
[hbr@VM-16-9-centos mklib]$ make hello
mkdir -p hello/lib
mkdir -p hello/include
cp -rf *.h hello/include
cp -rf *.a hello/lib
[hbr@VM-16-9-centos mklib]$ ll
total 36
drwxrwxr-x 4 hbr hbr 4096 Mar 19 23:30 hello
-rw-rw-r-- 1 hbr hbr 3066 Mar 19 23:30 libhello.a
-rw-rw-r-- 1 hbr hbr  327 Mar 19 23:29 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 19 22:53 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 19 22:52 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 19 23:30 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 19 22:43 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 19 22:43 myprint.h
-rw-rw-r-- 1 hbr hbr 1576 Mar 19 23:30 myprint.o
[hbr@VM-16-9-centos mklib]$ tree hello/
hello/
├── include
│   ├── mymath.h
│   └── myprint.h
└── lib
    └── libhello.a

2 directories, 3 files
  • hello/目录下,通过tree命令可以看到,头文件被正确地放在了include目录下,而静态库文件libhello.a被放在了lib目录下。这种组织方式便于管理和使用库文件。

5、使用静态库(导入系统)

我们把包含头文件和静态库的hello目录复制给uselib目录,我们接下来在uselib目录下操作。

[hbr@VM-16-9-centos mklib]$ cp -rf hello ../uselib/
[hbr@VM-16-9-centos uselib]$ ll
total 8
drwxrwxr-x 4 hbr hbr 4096 Mar 19 23:31 hello
-rw-rw-r-- 1 hbr hbr  157 Mar 19 22:58 main.c

我们将自定义静态库libhello.a和相关的头文件(mymath.hmyprint.h)安装到系统中,以便它们可以被系统范围内的其他程序使用。

  • 头文件gcc的默认搜索路径是: /usr/include
  • 库文件的默认搜索路径是: /lib64 or /usr/lib64
hbr@VM-16-9-centos uselib]$ tree hello
hello
├── include
│   ├── mymath.h
│   └── myprint.h
└── lib
    └── libhello.a

2 directories, 3 files
[hbr@VM-16-9-centos uselib]$ sudo cp hello/include/* /usr/include/ -rf
[hbr@VM-16-9-centos uselib]$ ls /usr/include/myprint.h
/usr/include/myprint.h
[hbr@VM-16-9-centos uselib]$ ls /usr/include/mymath.h
/usr/include/mymath.h
[hbr@VM-16-9-centos uselib]$ sudo cp -rf hello/lib/libhello.a /lib64
[hbr@VM-16-9-centos uselib]$ ls /lib64/libhello.a 
/lib64/libhello.a
  • sudo cp hello/include/* /usr/include/ -rf:这个命令将hello/include目录下的所有文件复制到/usr/include/目录。/usr/include是一个标准目录,用于存放系统级别的C/C++头文件。复制到这个目录后,编译器在编译任何程序时都能找到这些头文件。-r选项表示递归复制(包括子目录),-f表示在需要时覆盖已存在的文件。

  • ls /usr/include/myprint.hls /usr/include/mymath.h:这两个命令用来验证头文件是否成功复制到了/usr/include目录。如果文件存在,ls命令会显示文件路径,确认复制过程成功。

  • sudo cp -rf hello/lib/libhello.a /lib64:此命令将静态库libhello.a复制到/lib64目录。这个目录通常用来存放64位系统的库文件。将静态库复制到这里,链接器在链接程序时就能找到这个库。

  • ls /lib64/libhello.a:最后,这个命令用来验证静态库libhello.a是否成功复制到了/lib64目录。

通过这个过程,libhello.a库和它的头文件被安装到了系统级别的目录,使得开发者在编译和链接程序时可以轻松地引用这个库。这是确保库文件可用性和便于管理的一个常见做法。

下面我们来编译运行我们的程序。

[hbr@VM-16-9-centos uselib]$ gcc main.c -lhello
[hbr@VM-16-9-centos uselib]$ ll
total 20
-rwxrwxr-x 1 hbr hbr 8496 Mar 19 23:52 a.out
drwxrwxr-x 4 hbr hbr 4096 Mar 19 23:31 hello
-rw-rw-r-- 1 hbr hbr  157 Mar 19 22:58 main.c
[hbr@VM-16-9-centos uselib]$ ./a.out 
hello world[1710863572]
res:5050

编译程序

  • gcc main.c -lhello:这个命令使用gcc编译器来编译main.c文件,并链接libhello.a静态库。选项-lhello告诉编译器链接名为hello的库(默认搜索/lib、/usr/lib等标准库路径,因为之前已经将libhello.a复制到了/lib64,编译器能够找到并链接它)。
  • 编译器在编译时会查找main.c文件中包含的所有头文件,这些头文件之前已经被复制到了/usr/include,所以能够被编译器成功找到。
  • 编译成功后,会生成默认的可执行文件a.out

列出当前目录下的文件

  • ll命令(ls -l的别名)列出当前目录下的文件和目录,验证a.out可执行文件已经生成。

运行可执行文件

  • ./a.out运行编译出的程序。输出hello world[1710863572]res:5050,这表明程序运行成功。

6、删除静态库

[hbr@VM-16-9-centos uselib]$ sudo rm /usr/include/mymath.h
[hbr@VM-16-9-centos uselib]$ sudo rm /usr/include/myprint.h 
[hbr@VM-16-9-centos uselib]$ sudo rm /lib64/libhello.a
[hbr@VM-16-9-centos uselib]$ ll
total 20
-rwxrwxr-x 1 hbr hbr 8496 Mar 19 23:52 a.out
drwxrwxr-x 4 hbr hbr 4096 Mar 19 23:31 hello
-rw-rw-r-- 1 hbr hbr  157 Mar 19 22:58 main.c
[hbr@VM-16-9-centos uselib]$ rm a.out 
[hbr@VM-16-9-centos uselib]$ gcc main.c lhello
gcc: error: lhello: No such file or directory
[hbr@VM-16-9-centos uselib]$ gcc main.c 
main.c:1:10: fatal error: myprint.h: No such file or directory
 #include "myprint.h"
          ^~~~~~~~~~~
compilation terminated.

[hbr@VM-16-9-centos uselib]$ ls /usr/include/myprint.h
ls: cannot access /usr/include/myprint.h: No such file or directory
[hbr@VM-16-9-centos uselib]$ ls /usr/include/myprint.h
ls: cannot access /usr/include/myprint.h: No such file or directory

 7、使用静态库(不导入系统)

通过编译时指定自定义头文件的搜索路径以及库文件的搜索路径实现使用自定义静态库。

[hbr@VM-16-9-centos uselib]$ tree
.
├── hello
│   ├── include
│   │   ├── mymath.h
│   │   └── myprint.h
│   └── lib
│       └── libhello.a
└── main.c

3 directories, 4 files
[hbr@VM-16-9-centos uselib]$ gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello
[hbr@VM-16-9-centos uselib]$ ll
total 20
-rwxrwxr-x 1 hbr hbr 8496 Mar 20 00:12 a.out
drwxrwxr-x 4 hbr hbr 4096 Mar 19 23:31 hello
-rw-rw-r-- 1 hbr hbr  157 Mar 19 22:58 main.c
[hbr@VM-16-9-centos uselib]$ ./a.out 
hello world[1710864773]
res:5050

使用tree命令查看当前目录结构,显示有一个hello目录,里面包含includelib两个子目录。include目录下有头文件(mymath.hmyprint.h),lib目录下有静态库文件(libhello.a)。

编译命令

  • gcc main.c -I ./hello/include/ -L ./hello/lib/ -lhello
  • main.c是需要编译的源文件。
  • -I ./hello/include/告诉编译器在编译时包含./hello/include/目录作为额外的头文件搜索路径。这里的头文件指的是mymath.hmyprint.h
  • -L ./hello/lib/指定在链接时添加./hello/lib/目录作为库文件的搜索路径。这样编译器就能找到libhello.a静态库。
  • -lhello指示链接器链接名为hello的库,注意lib前缀和.a后缀在指定时省略。链接器会在上面通过-L指定的路径中查找libhello.a文件。

查看生成的可执行文件

  • 使用ll命令(或ls -l),可以看到生成了名为a.out的可执行文件。

二、动态库

1、概念

动态库(.so 文件),又称共享库,采用不同的方式工作。程序在运行时才链接到这些库的代码,而不是在编译时。这意味着可执行文件仅包含对其所需动态库函数入口地址的引用,而不是包含这些函数的实际代码。当程序启动时,操作系统从磁盘上的动态库文件中加载所需的代码到内存中,这个过程称为动态链接。

动态链接的主要优点是代码共享。

  • 由于动态库可以被多个程序共享,它们不仅减少了单个程序的文件大小,还节省了磁盘空间。
  • 此外,通过操作系统的虚拟内存机制,物理内存中的同一份动态库代码可以被所有需要它的进程所共享,这进一步优化了内存使用和减少了资源占用。

2、生成动态库(共享库)

首先,使用 -fPIC 编译选项生成位置无关码(Position Independent Code)。这意味着编译出的机器码能够在内存中的任意位置正确执行,这对于动态库是必要的,因为它们在加载时可能被操作系统映射到不同的地址空间。

  • 例如,对源文件 sub.c 和 add.c 进行编译生成对应的 .o 文件:
[root@localhost linux]# gcc -fPIC -c sub.c add.c

接下来,使用 -shared 链接选项将编译好的 .o 文件组合成一个动态库。按照Linux系统的约定,动态库的命名通常遵循 libxxx.so 的格式,其中 xxx 是库的基本名称。

[root@localhost linux]# gcc -shared -o libmymath.so add.o sub.o

完成以上步骤后,在当前目录下会有一个名为 libmymath.so 的动态链接库,同时可以看到所有相关的源文件和编译后的 .o 文件列表。

使用动态库: 在编译需要链接动态库的应用程序时,我们需要指定链接器去找到并链接这个库。

两个编译选项:

  • -L. 指定链接器查找库的目录,. 表示当前目录。
  • -lhello 告诉链接器链接名为 libhello.so 的动态库,这里的 hello 是库的基本名称,无需包含 lib 前缀和 .so 后缀。

例如,假设有一个已经编译好的主程序对象文件 main.o,我们可以这样编译链接它:

[root@localhost linux]# gcc main.o -o main -L. -lmymath
  • 这里 -lmymath 实际上是指链接名为 libmymath.so 的动态库。如果动态库不在系统默认的搜索路径中,则需要通过 -L 选项指明其所在目录。当运行此命令后,链接器将会把 main.o 和 libmymath.so 组合成一个可执行文件 main
[hbr@VM-16-9-centos mklib]$ gcc -fPIC -c mymath.c -o mymath.o
[hbr@VM-16-9-centos mklib]$ gcc -fPIC -c myprint.c -o myprint.o
[hbr@VM-16-9-centos mklib]$ ll
total 32
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:18 hello
-rw-rw-r-- 1 hbr hbr  327 Mar 22 14:18 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 22 14:18 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 22 14:18 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:22 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 22 14:18 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 22 14:18 myprint.h
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:22 myprint.o
[hbr@VM-16-9-centos mklib]$ gcc -shared myprint.o mymath.o -o libhello.so
[hbr@VM-16-9-centos mklib]$ ll
total 40
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:18 hello
-rwxrwxr-x 1 hbr hbr 8024 Mar 22 14:23 libhello.so
-rw-rw-r-- 1 hbr hbr  327 Mar 22 14:18 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 22 14:18 mymath.c
-rw-rw-r-- 1 hbr hbr   73 Mar 22 14:18 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:22 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 22 14:18 myprint.c
-rw-rw-r-- 1 hbr hbr   87 Mar 22 14:18 myprint.h
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:22 myprint.o

3、使用动态库的过程

从Linux操作系统内核的角度来讲解程序使用动态库的完整过程。

1. 编译阶段

在编译阶段,源代码被编译成目标文件(.o文件)。如果程序使用了动态库中的函数,编译器确保程序中对这些函数的调用不是直接的地址调用,而是留下一个标记(placeholder),表明这个函数调用的具体地址在链接阶段或者运行时确定。

2. 链接阶段

在链接阶段,链接器处理程序对动态库的依赖关系。对于动态链接,链接器不会把动态库的代码嵌入到最终的可执行文件中,而是在可执行文件中保留对动态库函数的引用(例如,通过符号表),并记录需要的动态库名和版本。

3. 加载阶段

当程序启动执行时,加载器(part of the operating system)负责将程序的可执行文件和它所需的动态库加载到内存中。加载器首先读取可执行文件的头部信息,找到程序依赖的动态库列表。

4. 动态链接

加载器接着会查找这些动态库文件,通常是在标准的系统库路径(如/lib/usr/lib等)或者通过环境变量(如LD_LIBRARY_PATH)指定的路径。找到动态库后,加载器将它们也加载到内存中。

5. 地址绑定

一旦动态库被加载到内存,加载器还需要解决符号绑定(symbol binding)问题,即将程序中对动态库函数的调用关联到实际的函数地址。这个过程可以在加载时(load-time binding)完成,或者是在程序运行时第一次调用到某个库函数时才进行(lazy binding,也称为运行时链接或late binding)。

6. 运行时

在程序运行过程中,如果执行到了一个需要动态库中函数的地方,CPU根据加载器设置的地址信息,跳转到动态库中相应函数的内存地址执行。这种机制允许多个程序共享同一动态库的同一份物理副本,减少了系统的总体内存占用。

4、Makefile同时创建动静态库

.PHONY:all
all:libhello.so libhello.a

libhello.so:mymath_d.o myprint_d.o
	gcc -shared mymath_d.o myprint_d.o -o libhello.so
mymath_d.o:mymath.c
	gcc -c -fPIC mymath.c -o mymath_d.o
myprint_d.o:myprint.c
	gcc -c -fPIC myprint.c -o myprint_d.o

libhello.a: mymath.o myprint.o
	ar -rc libhello.a mymath.o myprint.o
mymath.o:mymath.c
	gcc -c mymath.c -o mymath.o
myprint.o:myprint.c
	gcc -c myprint.c -o myprint.o

.PHONY:output
output:
	mkdir -p output/lib
	mkdir -p output/include
	cp -rf *.h output/include
	cp -rf *.a output/lib
	cp -rf *.so output/lib

.PHONY:clean
clean:
	rm -rf *.o *.a *.so output
  1. .PHONY:声明了phony target(伪目标),这些目标不是文件而是由Makefile处理的动作。all 是默认的目标,当执行 make 时不带任何参数时就会执行 all 下面定义的任务。

    .PHONY: all
    all: libhello.so libhello.a
  2. all 目标下依赖于两个动态链接库 libhello.so 和静态链接库 libhello.a,当执行 make all 时,这两个库都会被构建

  3. 动态和静态对象文件的生成规则,分别编译对应的源码文件生成相应的 .o 文件,使用 -c 参数表示仅进行编译,不进行链接。

    mymath_d.o: mymath.c
        gcc -c -fPIC mymath.c -o mymath_d.o

    其中,-fPIC 参数表示生成 Position Independent Code (PIC),这是生成共享库所必需的。

  4. .PHONY: output 定义了一个新的伪目标 output,当执行 make output 时,会创建所需的输出目录结构并复制头文件和库文件至相应目录。

    output:
        mkdir -p output/lib
        mkdir -p output/include
        cp -rf *.h output/include
        cp -rf *.a output/lib
        cp -rf *.so output/lib
  5. .PHONY: clean 定义了一个清理目标,用于删除中间产生的 .o 文件、库文件以及整个输出目录。

    clean:
        rm -rf *.o *.a *.so output
[hbr@VM-16-9-centos mklib]$ make
gcc -c -fPIC mymath.c -o mymath_d.o
gcc -c -fPIC myprint.c -o myprint_d.o
gcc -shared mymath_d.o myprint_d.o -o libhello.so
ar -rc libhello.a mymath.o myprint.o
[hbr@VM-16-9-centos mklib]$ ll
total 48
-rw-rw-r-- 1 hbr hbr 3114 Mar 22 14:50 libhello.a
-rwxrwxr-x 1 hbr hbr 8024 Mar 22 14:50 libhello.so
-rw-rw-r-- 1 hbr hbr  603 Mar 22 14:50 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 22 14:18 mymath.c
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:50 mymath_d.o
-rw-rw-r-- 1 hbr hbr   73 Mar 22 14:18 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:22 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 22 14:18 myprint.c
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:50 myprint_d.o
-rw-rw-r-- 1 hbr hbr   87 Mar 22 14:18 myprint.h
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:22 myprint.o
[hbr@VM-16-9-centos mklib]$ make output
mkdir -p output/lib
mkdir -p output/include
cp -rf *.h output/include
cp -rf *.a output/lib
cp -rf *.so output/lib
[hbr@VM-16-9-centos mklib]$ ll
total 52
-rw-rw-r-- 1 hbr hbr 3114 Mar 22 14:50 libhello.a
-rwxrwxr-x 1 hbr hbr 8024 Mar 22 14:50 libhello.so
-rw-rw-r-- 1 hbr hbr  603 Mar 22 14:50 makefile
-rw-rw-r-- 1 hbr hbr  146 Mar 22 14:18 mymath.c
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:50 mymath_d.o
-rw-rw-r-- 1 hbr hbr   73 Mar 22 14:18 mymath.h
-rw-rw-r-- 1 hbr hbr 1272 Mar 22 14:22 mymath.o
-rw-rw-r-- 1 hbr hbr   97 Mar 22 14:18 myprint.c
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:50 myprint_d.o
-rw-rw-r-- 1 hbr hbr   87 Mar 22 14:18 myprint.h
-rw-rw-r-- 1 hbr hbr 1624 Mar 22 14:22 myprint.o
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:52 output
[hbr@VM-16-9-centos mklib]$ tree output/
output/
├── include
│   ├── mymath.h
│   └── myprint.h
└── lib
    ├── libhello.a
    └── libhello.so

2 directories, 4 files

5、使用动态库

ldd命令

ldd 是一个在类 Unix 系统中常用的命令行工具,主要用于显示给定可执行文件或共享对象(例如动态链接库)的动态链接器依赖关系。它可以帮助用户查看一个可执行文件在运行时需要哪些动态链接库(.so 文件),以及这些库的具体路径。

  • ldd 是一个非常有用的诊断工具,它帮助开发人员和系统管理员确定一个程序能否成功运行,以及它的动态链接库依赖关系是否满足。

运行 ldd a.out 时,会发生以下事情:

  1. 解析依赖: ldd 会解析 a.out 文件中的动态链接信息,找出它依赖的所有动态链接库。

  2. 显示库信息: 对于每一个依赖的库,ldd 会显示出库文件的全路径(如果库存在于系统的搜索路径中),以及库与可执行文件的连接方式(如果是静态链接则不会有此信息)。

    输出格式通常类似于:

    [library_name] => [library_path] (0x[address])
  3. 检查库的可执行性: 如果 ldd 发现某个依赖库无法找到或者无法被执行,它会在输出中显示类似 "not found" 的信息,这表明在当前环境下,程序可能无法正常运行,因为缺失了必要的动态链接库。

动静态库同时存在优先使用动态库

[hbr@VM-16-9-centos DLLlib]$ cd uselib/
[hbr@VM-16-9-centos uselib]$ ll
total 8
-rw-rw-r-- 1 hbr hbr  157 Mar 22 14:53 main.c
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:54 output
[hbr@VM-16-9-centos uselib]$ tree
.
├── main.c
└── output
    ├── include
    │   ├── mymath.h
    │   └── myprint.h
    └── lib
        ├── libhello.a
        └── libhello.so

3 directories, 5 files
[hbr@VM-16-9-centos uselib]$ ll
total 8
-rw-rw-r-- 1 hbr hbr  157 Mar 22 14:53 main.c
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:54 output
[hbr@VM-16-9-centos uselib]$ cat main.c 
#include "myprint.h"
#include "mymath.h"

int main()
{
    Print("hello world");
    int res=addToTarget(1,100);
    printf("res:%d\n",res);
    return 0;
}
[hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello
[hbr@VM-16-9-centos uselib]$ ls
a.out  main.c  output
[hbr@VM-16-9-centos uselib]$ ldd a.out
        linux-vdso.so.1 =>  (0x00007ffc7f7fc000)
        libhello.so => not found
        libc.so.6 => /lib64/libc.so.6 (0x00007fa02bb97000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fa02bf65000)

在上述情景中,您编译了一个程序 main.c,该程序依赖于 libhello 库。libhello 库同时提供了静态库(libhello.a)和动态库(libhello.so)。执行如下命令编译程序时:

gcc main.c -I output/include/ -L output/lib/ -lhello

这里的 -lhello 参数告诉编译器链接名为 libhello 的库。在 Linux 系统中,如果同时存在这两种类型的库,GCC 编译器默认优先链接动态库(.so),而不是静态库(.a)。

然而,编译完成后运行 ldd a.out 检查动态链接时发现:

ldd a.out
...
libhello.so => not found

这表明虽然编译时链接了动态库 libhello.so,但是运行时系统在默认的库路径中并未找到这个动态库。这意味着即便编译时选择了动态库,但由于运行时环境的库路径配置问题,程序仍然无法成功运行。这个问题我们稍后解决!

 去掉动态库程序使用静态库

[hbr@VM-16-9-centos uselib]$ mv output/lib/libhello.so .
[hbr@VM-16-9-centos uselib]$ tree
.
├── a.out
├── libhello.so
├── main.c
└── output
    ├── include
    │   ├── mymath.h
    │   └── myprint.h
    └── lib
        └── libhello.a

3 directories, 6 files
[hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello
[hbr@VM-16-9-centos uselib]$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007fff0a356000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f028453d000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f028490b000)

-static静态链接 

[hbr@VM-16-9-centos uselib]$ mv libhello.so output/lib/
[hbr@VM-16-9-centos uselib]$ tree
.
├── a.out
├── main.c
└── output
    ├── include
    │   ├── mymath.h
    │   └── myprint.h
    └── lib
        ├── libhello.a
        └── libhello.so

3 directories, 6 files
[hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello -static
[hbr@VM-16-9-centos uselib]$ ldd a.out 
        not a dynamic executable
  1. 移动库文件: 用户将之前移动到uselib/根目录下的libhello.so动态链接库文件重新移回output/lib/目录。

  2. 显示目录结构: 用户再次使用tree命令查看目录结构,确认libhello.so已经回到了output/lib/目录下。

  3. 编译主程序并静态链接库: 用户运行gcc编译器,编译main.c文件,并指定头文件目录为output/include/,库文件目录为output/lib/,同时使用-lhello选项链接名为libhello的库,并通过-static选项指示编译器静态链接库。静态链接意味着编译后的可执行文件a.out将包含所有依赖库的代码,不再需要在运行时寻找动态链接库。

  4. 检查可执行文件依赖: 用户使用ldd命令检查新生成的a.out文件的动态链接库依赖关系。由于此次编译时使用了-static选项,a.out已经是静态链接的可执行文件,因此ldd工具报告not a dynamic executable,说明这个可执行文件不再依赖于任何动态链接库,所有的库代码已经被嵌入到了可执行文件内部。

6、解决无法链接动态库

[hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello
[hbr@VM-16-9-centos uselib]$ ll
total 20
-rwxrwxr-x 1 hbr hbr 8400 Mar 22 15:47 a.out
-rw-rw-r-- 1 hbr hbr  157 Mar 22 14:53 main.c
drwxrwxr-x 4 hbr hbr 4096 Mar 22 14:54 output
[hbr@VM-16-9-centos uselib]$ ./a.out 
./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

设置 LD_LIBRARY_PATH 环境变量

在运行程序前,可以临时将库路径添加到环境变量 LD_LIBRARY_PATH 中。

[hbr@VM-16-9-centos lib]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/hbr/linux/file_system/DLL\&SL/DLLlib/uselib/output/lib
[hbr@VM-16-9-centos lib]$ echo $LD_LIBRARY_PATH
/opt/rh/devtoolset-7/root/usr/lib64:/opt/rh/devtoolset-7/root/usr/lib:/opt/rh/devtoolset-
7/root/usr/lib64/dyninst:/opt/rh/devtoolset-7/root/usr/lib/dyninst:/opt/rh/devtoolset-
7/root/usr/lib64:/opt/rh/devtoolset-
7/root/usr/lib::/home/hbr/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/hbr/.VimForCpp/vim/
bundle/YCM.so/el7.x86_64:/home/hbr/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/hbr/linux/
file_system/DLL&SL/DLLlib/uselib/output/lib

这种方式设置 LD_LIBRARY_PATH 主要有以下潜在弊端:

  1. 临时性:这种方式只在当前 shell 会话中生效,一旦退出该会话或打开一个新的终端窗口,设置将会失效。如果你想永久修改 LD_LIBRARY_PATH,可以将该行添加到用户配置文件(如 ~/.bashrc~/.bash_profile 或相应 shell 的启动文件)中。

  2. 环境污染:每次在 LD_LIBRARY_PATH 中添加自定义路径,都会增加潜在的库冲突风险。因为系统在查找动态库时,除了标准库路径外还会额外搜索你添加的路径,可能导致加载到非预期版本的库。

  3. 安全性:恶意程序可能利用修改过的 LD_LIBRARY_PATH 加载恶意动态库,造成安全隐患。

添加配置文件

通过添加配置文件,避免了直接修改环境变量 LD_LIBRARY_PATH,而是通过系统级别的配置让动态链接器能够自动定位到自定义库位置,这种方法更加持久有效且不易引起环境变量污染的问题。 

[hbr@VM-16-9-centos lib]$ sudo touch /etc/ld.so.conf.d/mytest.conf
[hbr@VM-16-9-centos lib]$ ls /etc/ld.so.conf.d/ -l
total 16
-rw-r--r-- 1 root root 26 Jul 19  2023 bind-export-x86_64.conf
-rw-r--r-- 1 root root 19 Aug  9  2019 dyninst-x86_64.conf
-r--r--r-- 1 root root 63 Sep 13  2023 kernel-3.10.0-1160.99.1.el7.x86_64.conf
-rw-r--r-- 1 root root 17 Oct  2  2020 mariadb-x86_64.conf
-rw-r--r-- 1 root root  0 Mar 22 16:23 mytest.conf
[hbr@VM-16-9-centos uselib]$ sudo vim /etc/ld.so.conf.d/mytest.conf
[hbr@VM-16-9-centos uselib]$ sudo ldconfig
[hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello
[hbr@VM-16-9-centos uselib]$ ./a.out 
hello world[1711097794]
res:5050
[hbr@VM-16-9-centos uselib]$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffe1ef9f000)
        libhello.so => /home/hbr/linux/file_system/DLL&SL/DLLlib/uselib/output/lib/libhello.so (0x00007f3ee19a0000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f3ee15d2000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3ee1ba2000)
  1. 创建配置文件

    [hbr@VM-16-9-centos lib]$ sudo touch /etc/ld.so.conf.d/mytest.conf

    在 /etc/ld.so.conf.d/ 目录下创建一个名为 mytest.conf 的新文件,这个目录通常用来存放系统动态链接器(ld.so)在初始化时读取的库配置文件。sudo 是为了获取必要的管理员权限来编辑系统配置文件。

  2. 查看配置文件目录

    [hbr@VM-16-9-centos lib]$ ls /etc/ld.so.conf.d/ -l

    列出 /etc/ld.so.conf.d/ 目录下的所有文件及其详细属性,确认新创建的 mytest.conf 文件已存在。

  3. 编辑配置文件

    [hbr@VM-16-9-centos uselib]$ sudo vim /etc/ld.so.conf.d/mytest.conf

    使用 Vim 编辑器打开 mytest.conf 文件,并在此文件中添加要包含的库路径/home/hbr/linux/file_system/DLL&SL/DLLlib/uselib/output/lib/。 

  4. 更新库缓存

    [hbr@VM-16-9-centos uselib]$ sudo ldconfig

    执行 ldconfig 命令,它会读取 /etc/ld.so.conf.d/ 下的所有配置文件以及默认的 /etc/ld.so.conf 文件,然后扫描这些配置中指定的目录,并将其中的动态库文件信息加入到系统的动态链接器缓存 /etc/ld.so.cache 中。这样一来,后续运行的程序在寻找动态库时就会考虑这些新增的路径。

  5. 编译链接程序

    [hbr@VM-16-9-centos uselib]$ gcc main.c -I output/include/ -L output/lib/ -lhello

    使用 gcc 编译器编译 main.c 文件,同时指定了头文件搜索路径 -I output/include/ 和库文件搜索路径 -L output/lib/。由于之前已经通过 ldconfig 更新了系统库缓存,所以即使没有明确在命令行中指定 -Wl,-rpath 参数,如果 output/lib/ 路径已经被正确添加到了系统库配置中,编译后的程序也能找到 libhello.so 动态库。

  6. 运行程序并验证

    [hbr@VM-16-9-centos uselib]$ ./a.out

    执行编译后的 a.out 程序,可以看到程序成功运行并输出了预期结果。

  7. 检查动态链接依赖关系

    [hbr@VM-16-9-centos uselib]$ ldd a.out

    使用 ldd 命令来查看 a.out 程序的动态链接情况,证实了 libhello.so 动态库确实是从之前添加到系统配置中的 /home/hbr/linux/file_system/DLL&SL/DLLlib/uselib/output/lib/ 路径加载的。

  8. 删除配置文件

    [hbr@VM-16-9-centos uselib]$ sudo rm /etc/ld.so.conf.d/mytest.conf
    [sudo] password for hbr: 
    [hbr@VM-16-9-centos uselib]$ sudo ldconfig
    [hbr@VM-16-9-centos uselib]$ ./a.out 
    ./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory
    [hbr@VM-16-9-centos uselib]$ ldd a.out
            linux-vdso.so.1 =>  (0x00007ffd773cc000)
            libhello.so => not found
            libc.so.6 => /lib64/libc.so.6 (0x00007f604941f000)
            /lib64/ld-linux-x86-64.so.2 (0x00007f60497ed000)

建立软链接

使用 ln -s 命令在 /lib64 目录下建立软链接。

[hbr@VM-16-9-centos uselib]$ sudo ln -s /home/hbr/linux/file_system/DLL\&SL/DLLlib/uselib/output/lib/libhello.so /lib64/libhello.so
[sudo] password for hbr: 
[hbr@VM-16-9-centos uselib]$ ls /lib64/libhello.so -l
lrwxrwxrwx 1 root root 71 Mar 22 18:07 /lib64/libhello.so -> /home/hbr/linux/file_system/DLL&SL/DLLlib/uselib/output/lib/libhello.so
[hbr@VM-16-9-centos uselib]$ ./a.out 
hello world[1711102075]
res:5050
[hbr@VM-16-9-centos uselib]$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffc335a0000)
        libhello.so => /lib64/libhello.so (0x00007f3a27516000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f3a27148000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f3a27718000)

删除软链接

[hbr@VM-16-9-centos uselib]$ sudo rm /lib64/libhello.so
[hbr@VM-16-9-centos uselib]$ ls /lib64/libhello.so -l
ls: cannot access /lib64/libhello.so: No such file or directory
[hbr@VM-16-9-centos uselib]$ ./a.out 
./a.out: error while loading shared libraries: libhello.so: cannot open shared object file: No such file or directory

相关推荐

  1. Linux 文件系统静态

    2024-03-23 13:02:03       22 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-23 13:02:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-23 13:02:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-23 13:02:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-23 13:02:03       18 阅读

热门阅读

  1. Keepalived 踩坑

    2024-03-23 13:02:03       24 阅读
  2. ArrayList的常用方法

    2024-03-23 13:02:03       16 阅读
  3. 第三十章 配置 Web Gateway 的默认参数 - ASP 重定向

    2024-03-23 13:02:03       16 阅读
  4. 什么是React

    2024-03-23 13:02:03       22 阅读
  5. Hive正则表达式使用

    2024-03-23 13:02:03       21 阅读
  6. 大厂面试--介绍下webpack的整个生命周期?

    2024-03-23 13:02:03       16 阅读
  7. vue改变data值但页面不刷新

    2024-03-23 13:02:03       19 阅读