我正在使用openCV库进行计算机视觉研究,在编译时我遇到了一些问题,这些问题让我试图了解操作系统如何将库与源代码链接起来。在互联网上寻找一段时间以获得一个很好的概述和阅读g ++ / gcc手册,ld ...我得出一些结论,我希望有更多经验的人来解释我。
首先是我使用的编译行。这是:
- 输入:g++
pkg-config --cflags --libs opencv image-conversion.cpp -o image-conversion
输出:可执行代码
输入:pkg-config --cflags --libs opencv
输出:-I/opt/local/include/opencv -I/opt/local/include -L/opt/local/lib -lopencv_shape -lopencv_stitching -lopencv_objdetect -lopencv_superres -lopencv_videostab -lopencv_calib3d -lopencv_features2d -lopencv_highgui -lopencv_videoio -lopencv_imgcodecs -lopencv_video -lopencv_photo -lopencv_ml -lopencv_imgproc -lopencv_flann -lopencv_core
我的代码所需的库是-lopenhighgui
,但我更喜欢以这种方式编译,因为这个库依赖于其他库。问题是,当我去/opt/local/lib
查看库时,我有三个文件:
-libopencv_highgui.3.1.0.dylib
-libopencv_highgui.3.1.dylib
-libopencv_highgui.dylib
我不知道-lopenhighgui
引用了哪个库。我在g ++手册中发现-l
标志指定库名称避免lib
前缀和*.a
*.so
(linux)/ *.dylib
(mac)后缀。执行otool -L
可执行文件后,我得到输出:
/opt/local/lib/libopencv_highgui.3.1.dylib (compatibility version 3.1.0, current version 3.1.0)
那么为什么它使用这个而不是其他的,它的使用方式是什么?三个图书馆有什么区别?
另一个问题是关于链接和执行过程。我已经理解了使用静态库时的链接过程。我的问题是编译动态库时。在下一个例子中:
- 输入:g++ -I/opt/local/include/opencv -I/opt/local/include -L/opt/local/lib -lopencv_highgui image-conversion.cpp -o image-conversion
我发现从编译到执行程序的过程可以分为三个部分。
第一个编译器查找标准目录或目录下的头文件,通过-I
标志来进行代码解析。
其次,它在路径-L或标准路径下链接由-lflag
指定的库。这是通过链接器(ld)
第三,当执行时,动态链接程序(dyld)是负责将代码“添加”到RAM存储器以便执行的程序。
问题是下一个:我在互联网上发现一些人说,如果他们没有设置$LD_LIBRARY_PATH
(在linux中)或$DYLD_LIBRARY_PATH
(在mac OSx中)与非库的标准目录(在我的例子中是export LD_LIBRARY_PATH="/opt/local/lib"
),动态链接器找不到库,程序执行失败。我发现我的程序没有崩溃,如果我执行otool(用于查看链接的内容)。我得到这个(这是链接的所有库的摘要):
/opt/local/lib/libopencv_shape.3.1.dylib (compatibility version 3.1.0, current version 3.1.0)
/opt/local/lib/libopencv_imgproc.3.1.dylib (compatibility version 3.1.0, current version 3.1.0)
...
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1226.10.1)
发生了什么事?我发现LD_LIBRARY_PATH可以用来测试新的库,但是为什么这个人说他们只需要设置这个变量来执行程序呢?最后一部分如何运作?我没有设置它,它完美地工作。
感谢任何人。
答案 0 :(得分:1)
实际上有两个问题(不被认为是非常好的SO练习,顺便说一句),所以我会独立回答它们。
另外,我将专注于Linux - 这是我熟悉的领域。从问题的文本中,我假设Mac世界中的事情非常相似。
在Linux环境中使用.so域时,人们经常会看到动态库通常以三元组的形式呈现。例如,库foo可能存在于3个文件中: libfoo.so , libfoo.so.6 , libfoo.so.6.5.4 。如果仔细观察,你会发现它们都是同一个文件 - 通常其中两个只是第三个文件的符号链接。为了进一步讨论,libfoo.so将被称为 unversioned 库,ibfoo.so.6将被称为 major-versioned 和libfoo.so.6.5.4 * full -versioned。你为什么需要这些?为了更好的版本控制。
当你链接你的应用程序时,你总是使用一个链接器规则用于未版本化的livbary - 考虑到链接器将lib
和.so
添加到规则的事实,它看起来像
g++ ... -lfoo ...
当你的应用程序被链接时,链接器会打开libfoo.so并检查它的几个部分。它检查的内容是所谓的SONAME
标题。链接.so库时会创建此标头,并且它可以与当前正在查看的链接器具有不同的文件名。例如,它可能有一个主要版本的文件,链接器将看到它:SONAME = libfoo.so.6。
当链接器看到SONAME时,它会将生成的应用程序文件标记为需要libfoo.so.6
- 即使您确实要求libfoo.so
。
通过这样做,链接器保留了库的某个版本。您的应用程序最初编译并与版本6链接,因此无论何时运行应用程序,都需要版本6.
如果稍后系统升级(或应用程序在不同的系统上运行)foo
的版本不同(例如7),则.so文件会有所不同: libfoo.so < / em>, libfoo.so.7 , libfoo.so.7.6.5 。由于您的应用需要libfoo.so.6
,它将无法启动 - 这是一件好事,因为谁知道版本7仍然兼容?如果没有这种保护,应用程序将启动并使用不同的库版本,效果可能是毁灭性的。
你的第二个问题是LD_LIBRARY_PATH。这是事实,运行时链接器在查找动态库时会查询此变量。但是,它不是唯一可以咨询的东西。除此之外,还有默认搜索路径,还有一个每应用程序动态库路径,它在链接时记录在应用程序中,通常由链接器的rpath参数控制,如:
g++ ... -Wl,rpath,/path/to/so/library
当像这样记录路径时,运行时链接器会在加载应用程序时将这些路径添加到搜索路径列表中。
在没有LD_LIBRARY_PATH的情况下为您的应用程序找到库的事实意味着以下两种情况之一:链接应用程序时记录了rpath,或者平台上的默认搜索路径中实际包含了/opt/local/lib
。