我正在使用 编译模型make
。该模型有一个Makefile
通过类似于 的标志将源代码与依赖库连接起来-L/lib1 -L/lib2
。但是当我尝试运行该模型时,它会失败,除非我还确保环境变量
export LD_LIBRARY_PATH=/lib1:/lib2
并指向完全相同的库。这对我来说似乎多余。
这里到底发生了什么?为什么我必须在编译之前和执行之前指定库的位置?
这可能是一个愚蠢的问题;我对编译机器代码不太有经验,通常只使用脚本语言。
答案1
尽管每个人都在通俗意义上使用编译,将源代码转换为可执行文件,但从技术上讲,它只是相当长的管道中的一个步骤:
- 输入文件通过预处理器运行,产生单个翻译单元。
- 预处理器的输出被编译为程序集。
- 汇编器将其作为输入并输出目标文件。
- 链接器将目标文件拼接在一起以生成可执行文件。
[ 严格地说,没有必要将这些步骤分开,现代编译器通常将它们结合起来以提高效率。]
我们关心的是链接步骤,它将您的代码与标准系统库结合起来。链接器将对象从静态库直接复制到可执行文件中。然而,对于共享库,它仅提供对该库的引用。
共享库有很多优点。您可以更新它们而无需重新编译程序,并且它们使用更少的内存,因为程序可以共享通用代码。它们还有一个明显的缺点,即代码不在可执行文件中。
解决方案是动态加载器,它负责在运行时解析所有共享库引用。装载机自动运行;这样做的说明是链接器包含在可执行文件中的一件事。当然,这前提是加载器可以找到这些库。
系统库位于标准目录中,这很简单。如果不是这种情况,加载程序将搜索 LD_LIBRARY_PATH。为什么链接器不直接将路径放入可执行文件中?因为那样你就无法移动或更改库。
实际上,您也无法真正移动可执行文件,因为该库位于系统搜索路径之外。如果它仅在库位于 ~luke/lib 中时运行,那么您不能将其交给 joe,除非他可以读取您的文件。如果你换了一份新工作,乔就很糟糕。
仅供参考,它还会以多种其他方式吸收。如果您只能在编译时指定库位置等,那么调试将成为一场永恒的噩梦。