gpt4 book ai didi

c++ - cpp : usr/bin/ld: cannot find -l

转载 作者:行者123 更新时间:2023-12-01 23:14:38 31 4
gpt4 key购买 nike

我创建了一个cpp项目,该项目使用了一个名为libblpapi3_64.so的lib文件
该文件来自我从Internet下载的库。

我的项目运行没有任何错误。所以我将其更新为bitbucket。
然后我的同事下载并在自己的计算机上运行它。但是他得到了一个错误:
usr/bin/ld: cannot find -lblpapi3_64

实际上,我已将其复制到项目存储库中。我的意思是我在项目下创建了一个名为lib的文件,并且我使用的所有lib文件都在其中。

还有其他lib文件,例如liblog4cpp.a,但它们都很好。仅libblpapi3_64.so收到错误。

是否因为它是.so文件而不是.a文件?还是还有其他原因?
顺便说一句,libblpapi3_64.so的文件名是green,其他文件(.a)是white。我认为这不是链接文件,而是原始文件。

最佳答案

简介:
ld不知道您的项目库位于何处。您必须将其放置在ld的已知目录中,或通过链接器的-L参数指定库的完整路径。

为了能够构建程序,您需要将库放在/bin/ld搜索路径中,并且您的同事也必须位于。为什么?查看详细答案。

详细信息:

首先,我们应该了解哪些工具可以做什么:

  • 编译器生成带有未解析符号的简单object files(它在运行时不太在乎符号)。
  • 链接器将许多objectarchive files组合在一起,重新定位它们的数据并将符号引用绑定(bind)到一个文件中:可执行文件或库。

  • 让我们从一个例子开始。例如,您有一个包含3个文件的项目: main.cfunc.hfunc.c

    main.c
    #include "func.h"
    int main() {
    func();
    return 0;
    }

    func.h
    void func();

    func.c
    #include "func.h"
    void func() { }

    因此,当您将源代码( main.c)编译为目标文件( main.o)时,由于它具有未解析的符号,因此无法运行。让我们从 producing an executable工作流程的开头开始(没有详细信息):

    预处理器在其工作后产生以下 main.c.preprocessed:
    void func();
    int main() {
    func();
    return 0;
    }

    和以下 func.c.preprocessed:
    void func();
    void func() { }

    就像您在 main.c.preprocessed中看到的那样,您的 func.c文件和 void func()的实现都没有连接,编译器根本不知道它,它会分别编译所有源文件。因此,要编译该项目,您必须使用 cc -c main.c -o main.occ -c func.c -o func.o之类的东西来编译两个源文件,这将产生2个目标文件 main.ofunc.ofunc.o可以解决所有符号问题,因为它只有一个函数在 func.c内部编写了主体,但是 main.o尚未解决 func符号,因为它不知道在哪里实现。

    让我们看看 func.o里面是什么:
    $ nm func.o
    0000000000000000 T func

    简单来说,它包含一个在文本代码部分中的符号,因此这是我们的 func函数。

    让我们看一下 main.o:
    $ nm main.o
    U func
    0000000000000000 T main

    我们的 main.o具有已实现并已解析的静态函数 main,我们可以在目标文件中看到它。但是我们还看到 func符号,标记为未解析的 U,因此我们无法看到其地址偏移量。

    为了解决该问题,我们必须使用链接器。它将获取所有目标文件并解析所有这些符号(在我们的示例中为 void func();)。如果链接器因某种原因无法执行此操作,则会引发类似 unresolved external symbol: void func()的错误。如果不将 func.o对象文件提供给链接器,则可能会发生这种情况。因此,让我们将所有需要的目标文件提供给链接器:
    ld main.o func.o -o test

    链接器将通过 main.o,然后通过 func.o,尝试解析符号,如果一切正常-将其输出到 test文件。如果我们查看产生的输出,我们将看到所有符号都已解析:
    $ nm test 
    0000000000601000 R __bss_start
    0000000000601000 R _edata
    0000000000601000 R _end
    00000000004000b0 T func
    00000000004000b7 T main

    至此我们的工作完成了。让我们看一下动态(共享)库的情况。让我们从 func.c源文件中创建一个共享库:
    gcc -c func.c -o func.o
    gcc -shared -fPIC -Wl,-soname,libfunc.so.1 -o libfunc.so.1.5.0 func.o

    瞧,我们有。现在,让我们将其放入已知的动态链接器库路径 /usr/lib/中:
    sudo mv libfunc.so.1.5.0 /usr/lib/ # to make program be able to run
    sudo ln -s libfunc.so.1.5.0 /usr/lib/libfunc.so.1 #creating symlink for the program to run
    sudo ln -s libfunc.so.1 /usr/lib/libfunc.so # to make compilation possible

    让我们通过在编译和静态链接过程后不解析 func()符号,创建可执行文件并将其(动态)链接到我们的共享库( libfunc),使我们的项目依赖于该共享库:
    cc main.c -lfunc

    现在,如果我们在符号表中查找符号,则符号仍未解析:
    $ nm a.out | grep fun
    U func

    但这不再是问题,因为 func符号将在每个程序启动之前由动态加载程序解析。好的,现在让我们回到理论上。

    实际上,库只是对象文件,通过使用 ar工具和由 ranlib工具创建的单个符号表将它们放置在单个存档中。

    编译目标文件时,编译器无法解析 symbols。这些符号将由链接器替换为地址。因此,可以通过两件事来解析符号: the linkerdynamic loader:
  • 链接器:ld,完成2个工作:

    a)对于静态库或简单的目标文件,此链接器将目标文件中的外部符号更改为真实实体的地址。例如,如果我们使用C++名称,则链接器会将链接_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_更改为0x07f4123f0

    b)对于动态库,只有会检查,是否可以完全解析符号(您尝试与正确的库链接),但不会用地址替换符号。如果无法解析符号(例如,您未在链接到的共享库中实现符号)-它会引发undefined reference to错误并中断构建过程,因为您尝试使用这些符号,但链接器无法在其中找到此类符号这是它正在处理的目标文件。否则,此链接器会将一些信息添加到ELF可执行文件中,该信息是:

    一世。 .interp部分-请求interpreter-执行之前调用动态加载程序,因此此部分仅包含动态加载程序的路径。例如,如果您查看依赖于共享库(libfunc)的可执行文件,则会看到interp部分$ readelf -l a.out:
    INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
    0x000000000000001c 0x000000000000001c R 1
    [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

    ii。 .dynamic部分-interpreter在执行之前将查找的共享库的列表。您可以通过lddreadelf看到它们:
    $ ldd a.out
    linux-vdso.so.1 => (0x00007ffd577dc000)
    libfunc.so.1 => /usr/lib/libfunc.so.1 (0x00007fc629eca000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fefe148a000)
    /lib64/ld-linux-x86-64.so.2 (0x000055747925e000)

    $ readelf -d a.out

    Dynamic section at offset 0xe18 contains 25 entries:
    Tag Type Name/Value
    0x0000000000000001 (NEEDED) Shared library: [libfunc.so.1]
    0x0000000000000001 (NEEDED) Shared library: [libc.so.6]

    请注意,ldd还会找到文件系统中的所有库,而readelf仅显示程序需要的库。因此,所有这些库都将由动态加载程序搜索(下一段)。
    链接器在构建时间上工作。
  • 动态加载程序:ld.sold-linux。它查找并加载程序所需的所有共享库(如果之前未加载),通过在程序开始之前将它们替换为实际地址来解析符号,准备运行程序,然后运行它。在构建之后和运行程序之前,它可以工作在。简而言之,动态链接意味着在每个程序启动之前在可执行文件中解析符号。

  • 实际上,当您使用ELF节运行.interp可执行文件时(需要加载一些共享库),操作系统(Linux)首先运行解释器,但不运行程序。否则,您将具有未定义的行为-程序中有符号,但符号没有由地址定义,这通常意味着程序将无法正常运行。

    您也可以自己运行动态加载程序,但这不是必需的(对于32位架构的elf,二进制文件是/lib/ld-linux.so.2,对于64位架构的elf,二进制文件是/lib64/ld-linux-x86-64.so.2)。

    链接器为什么在您的情况下要求使用/usr/bin/ld: cannot find -lblpapi3_64?因为它尝试查找其已知路径中的所有库。为什么要在运行时加载该库,为什么要搜索该库?因为它需要检查该库是否可以解析所有需要的符号,并将其名称放入动态加载程序的.dynamic部分。实际上,.interp节几乎存在于每个c/c++精灵中,因为libclibstdc++库都是共享的,并且编译器默认情况下将任何项目动态链接到它们。您也可以静态链接它们,但这将扩大可执行文件的总大小。因此,如果找不到共享库,您的符号将仍然无法解析,并且您将 UNABLE 来运行您的应用程序,因此无法生成可执行文件。您可能会获得通常通过以下方式搜索库的目录列表:
  • 在编译器参数中将命令传递给链接器。
  • 通过解析ld --verbose的输出。
  • 通过解析ldconfig的输出。

  • 其中的一些方法在here中进行了说明。

    动态加载器尝试使用以下命令查找所有库:

    ELF文件的
  • DT_RPATH动态部分。
  • 可执行文件的
  • DT_RUNPATH部分。
  • LD_LIBRARY_PATH环境变量。
  • /etc/ld.so.cache-自己的缓存文件,其中包含以前在增强库路径中找到的候选库的已编译列表。
  • 默认路径:在默认路径/lib中,然后在/usr/lib中。如果二进制文件已使用-z nodeflib链接器选项链接,则将跳过此步骤。

  • ld-linux search algorithm

    另外,请注意,如果我们在谈论共享库,它们的名称不是.so,而是.so.version格式。当您构建应用程序时,链接器将查找.so文件(通常是.so.version的符号链接(symbolic link)),但是在您运行应用程序时,动态加载程序将查找.so.version文件。例如,假设我们有一个库test,根据semver的版本为1.1.1。在文件系统中,它将看起来像:
    /usr/lib/libtest.so -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1 -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1.1 -> /usr/lib/libtest.so.1.1.1
    /usr/lib/libtest.so.1.1.1

    因此,要能够进行编译,您必须具有所有版本控制的文件(libtest.so.1libtest.so.1.1libtest.so.1.1.1)和一个libtest.so文件,但是要运行您的应用程序,您首先必须仅列出3个版本控制的库文件。这也解释了为什么Debian或rpm软件包分别具有devel -packages:普通的软件包(仅包含已编译的应用程序运行它们所需的文件),具有3个版本化的库文件,以及一个devel软件包,仅包含用于创建的symlink文件可以编译项目。

    恢复

    毕竟:
  • 您,您的同事和应用程序代码的每个Each 用户必须在其系统链接器路径中拥有所有库,才能进行编译(构建应用程序)。否则,他们必须更改Makefile(或编译命令)以通过添加-L<somePathToTheSharedLibrary>作为参数来添加共享库位置目录。
  • 成功构建后,您还需要再次使用库才能运行该程序。您的库将由动态加载程序(ld-linux)搜索,因此它必须位于路径(请参见上文)或系统链接器路径中。在大多数linux程序发行版中,例如Steam发行的游戏,都有一个shell脚本来设置LD_LIBRARY_PATH变量,该变量指向游戏所需的所有共享库。
  • 关于c++ - cpp : usr/bin/ld: cannot find -l<nameOfTheLibrary>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30600978/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com