Linux依赖库路径(编译时)

26 篇文章 0 订阅
1 篇文章 0 订阅

0 前言    

    gcc编译程序时,可通“-L”、“-rpath”或“-rpath-link”参数指定依赖库路径,关于这3个参数的说明,有不少资料,但是看完了还是觉得模糊,分不清它们的区别。本文将用实验的方法去探讨这3个参数的区别。

1 源文件

    用于本文实验的3个源文件如下所示:

(1)world.c

#include<stdio.h>

void world(void)
{
    printf("world.\n");
}

(2)hello.c

#include <stdio.h>

void world(void);

void hello(void)
{
    printf("hello\n");
    world();
}

(3)test.c

void main(void)
{
    hello();
}

2 生成动态库

    参照《Linux静态库与动态库制作》,将hello.c和world.c分别生成动态库

gcc -c hello.c world.c
gcc -shared -o libhello.so hello.o
gcc -shared -o libworld.so world.o

     这时,生成的文件及其依赖如下图:

图2.1

    由上图可见,libhello.so和libworld都依赖linux-gate.so.1、libc.so.6以及/lib/ld-linux.so.2,并且这3个库的路径都已经硬编码进libhello.so和libworld.so中了(=>右边的部分)。

    然而,虽然libhello.so中调用了libworld.so的函数,但是在上图中并没有显示出此关系。为了达到使libhello.so依赖libworld.so的目的,在生成libhello.so时要链接到libworld.so:

gcc -shared -o libhello.so hello.o -lworld -L

   此时,再使用ldd查看libhello.so的依赖:

图2.2

    由上图可见,此时libhello.so已经依赖libworld.so。

3 编译test.c

3.1 -L

    由于test.c直接依赖libhello.so,因此使用-lhello -L

gcc test.c -lhello -L .

    结果如下图:

图3.1

    由上图可见已经在-L指定的路径找到了libhello.so,只是libhello.so还需要libworld.so。虽然它都在同一目录下,但是还是没有办法自动找到libworld.so。

    那么,能不能使用-lworld将libworld.so也一并链接到test.c中呢?下面做一个尝试:

gcc test.c -lhello -lworld -L .

     没有报错,成功生成a.out。

    执行a.out并且使用ldd查看a.out的依赖:

图3.2

    由上图可见,虽然使用-lworld参数将libworld.so链接到了a.out中,但是上面只显示a.out依赖libhello.so。由于找不到libhello.so(=> not found)的路径,因此需要设置环境变量LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/home/liyihai/documents

    再次执行a.out,并用ldd命令查看a.out的依赖库:

图3.3

       由上图可见,libhello.so已经通过LD_LIBRARY_PATH环境变量找到,并且libworld.so也出现在a.out的依赖中!

    结论:-L指定的是链接时的库路径,生成的可执行文件在运行时库的路径由LD_LIBRARY_PATH环境变量指定。

3.2 -rpath

    根据图3.1的提示,由于libhello.so依赖libworld.so,可以只用-rpath或者-rpath-link来指定。这里先使用-rpath。

    先清空LD_LIBRARY_PATH环境变量,然后重新编译test.c并且带上-rpath参数:

export LD_LIBRARY_PATH=
gcc test.c -lhello -L . -Wl,-rpath .

    执行a.out,并且使用ldd命令查看a.out的依赖:

图3.4

    由上图可见,虽然没有明确指出链接libworld.so,但是libworld.so还是出现在a.out的依赖中。

    另外,虽然LD_LIBRARY_PATH已经清空,但是a.out还是可以执行,这说明库的路径已经被硬编码进a.out中了。需要注意的是,libhello.so和libworld.so的路径都是通过-rpath指定的路径找到的。

3.2.1 实验1

    这时候,如果libhello.so和libworld.so的路径改变了,将会发生什么情况呢?下面做一个实验。

    创建一个lib_tmp目录,然后将libhello.so和libworld.so移动进这个目录。

mdir lib_tmp
mv libhello.so lib_tmp/
mv libworld.so lib_tmp/

    这时再执行a.out时,提示找不动态库,使用ldd命令查看a.out的库路径:

图3.5

    由上图红色圈部分可见,libhello.so的路径是not found的,并且libworld.so没有出现在其中。这和3.1的情况是相同的。

    究其原因,就是要先找到libhello.so再去找libworl.so,因为是libhello.so依赖libworld.so,而不是a.out依赖于libworld.so。

    由此可见,使用了-rpath参数指定库的路径后,生成的可执行文件的依赖库路径并非就固定不变了。而是执行时先从-rpath指定的路径去找依赖库,如果找不到,还是会报not found。

    那么,这时候,LD_LIBRARY_PATH对a.out是否还有影响呢?下面将LD_LIBRARY_PATH设为当前libhello.so和libworld.so所在的路径

export LD_LIBRARY_PATH=./lib_tmp

    再次执行a.out,并且使用ldd查看此时a.out的依赖库路径:

图3.6

    由上图可见LD_LIBRARY_PATH还是起作用的!由上图可见,和使用-rpath指定路径的效果是一样的。

3.2.2 实验2

    将LD_LIBRARY_PATH清空,然后将libhello.so移动到lib_tmp中,而libworld.so则留在documents目录中。

    执行a.out,并且使用ldd查看此时a.out的依赖库:

图3.7

    由上图可见,找不到libhello.so。这时,再指定LD_LIBRARY_PATH的路径为libhello.so所在的路径:

export LD_LIBRARY_PATH=./lib_tmp/

    再次执行a.out,并且使用ldd查看其依赖库:

图3.8

    由上图可见,一切又恢复了正常。此时,libhello.so是通过LD_LIBRARY_PATH找到的,而libworld.so则是通过-rpath指定的路径找到的。

3.2.3 回顾

    其实,经过测试,在3.1小节中,如果先指定LD_LIBRARY_PATH的值为libhello.so和libworld.so所在的路径,然后再编译test.c(执行3.1节的第1条编译命令),是可以成功编译的,并不会报图3.1的那种错误。也就是说,LD_LIBRARY_PATH不仅指定可执行文件的库路径,还指定了库所依赖其它库的路径。

3.2.4 结论   

    并非指定-rpath参数后,就抛弃LD_LIBRARY_PATH环境变量,只是多了个可选的依赖库路径而已。

3.3 -rpath-link

    先将LD_LIBRARY_PATH的值清空,然后将libworld.so移动到lib_tmp目录中,而libhello.so则留在documents目录中,使用以下命令对test.c进行编译:

gcc test.c -lhello  -L . -Wl,-rpath-link ./lib_tmp

   执行a.out并且使用ldd查看a.out的依赖库:

图3.9

   找不到 libhello.so,这在预料之中。下面指定LD_LIBRARY_PATH的值为libhello.so的路径,然后在执行a.out,并且查看a.out的依赖:

图3.10

    由上图可见,libhello.so已经通过LD_LIBRARY_PATH找到,但是libworld.so由于没有在LD_LIBRARY_PATH指定的路径中,而且编译时a.out又没有包含库的路径,因此找不到。这

    对比3.2.2可以得出结论:-rpath和-rpath-link都可以在链接时指定库的路径;但是运行时rpath-link指定的路径就不再有效(链接器没有将库的路径包含进可执行文件中),而-rpath指定的路径还有效(因为链接器已经将库的路径包含在可执行文件中了)

    最后,不管使用了-rpath还是-rpath-link,LD_LIBRARY_PATH还是有效的

4 总结

    三个变量/参数的生效时机总结如下:

编译(链接)时运行时备注
-LYN
-rpath-linkYN其作用类似于-L
-rpathYY优先级高于LD_LIBRARY_PATH
LD_RUN_PATHYY待验证[6]
LD_LIBRARY_PATHNY

参考资料

[1]动态库的链接和链接选项-L,-rpath-link,-rpath

[2]ld的-rpath与-rpath-link选项

[3]Shared Library Search Paths

[4]rpath - Wikipedia

[5]rpath添加依赖库搜索路径

[6]LD_RUN_PATH && LD_LIBRARY_PATH

Linux系统中,Makefile是一种常用的构建工具文件,用于自动化管理程序的编译过程。当你有多个源文件,并且其中一个或多个需要引用外部才能编译,Makefile可以帮助你处理这些依赖。 通常,在Makefile中,你会设置以下几个关键部分: 1. **规则(Rules)**:比如`%.o: %.c`,表示每个`.c`源文件都会生成一个`.o`目标文件。对于包含的文件,可能会增加类似`libname.o: libname.c $(LIBS)`这样的规则,其中`$(LIBS)`代表需要链接的列表。 2. **变量(Variables)**:例如`CC = gcc`, `CFLAGS = -Wall -g`, 定义了使用的编译器和编译选项。`LDLIBS = -L/path/to/library -llibrary_name`用于指定的位置和名。 3. **依赖(Dependencies)**:通过`-I`选项声明头文件目录,如`INCLUDES += -I/usr/include`, 通过`$(wildcard *.h)`获取所有头文件路径并加入到依赖链中。 4. **链接步骤(Linking step)**:使用`all: program`定义最终的目标,`program`由各个`.o`文件通过`$(CC) $(CFLAGS) $(OBJECTS) -o $@ $(LDLIBS)`链接生成。`$(OBJECTS)`是所有源文件经过编译后的.o文件名列表。 5. **清理规则(Clean rule)**: 如果你想添加一个命令来清除编译后的中间文件,可以添加一行`clean:` ```make clean: rm -f *.o program ``` 使用Makefile,只需要运行`make`命令,它会根据文件内容自动处理依赖编译和链接的过程。记得在实际操作前检查Makefile是否包含了所有正确的信息和路径
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OneSea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值