为什么动态链接时g ++会检测到未定义的引用

use*_*462 3 c++ g++ dynamic-linking

我可能对动态链接的工作方式感到误解,因为我无法弄清楚。据我了解,动态链接库时,其符号在运行时解析。从这个答案:

当您动态链接时,可执行文件中将包含指向要链接的文件的指针(例如,文件的文件名),并且链接时不包含该文件的内容。只有当您以后运行该可执行文件时,这些动态链接的文件才被购买,并且它们仅被购买到该可执行文件的内存中副本中,而不是磁盘中的一个副本中。

[...]

在动态情况下,主程序与C运行时导入库(声明动态库中的内容但未实际定义的内容)链接在一起。即使实际的代码丢失,这也允许链接器链接。

然后,在运行时,操作系统加载程序将主程序与C运行时DLL(动态链接库或共享库或其他命名法)进行后期链接。

我对为什么g++动态链接到共享对象时似乎期望共享对象在那里感到困惑。当然,我希望库的名称是必需的,以便可以在运行时加载它,但是为什么.so在此阶段它是必需的?此外,g++在链接库时,抱怨未定义的引用。

我的问题是:

  1. g++如果库的加载仅在运行时发生,为什么在动态链接共享对象时似乎需要共享对象?我知道-l指定共享对象的名称可能需要该标志,以便可以在运行时加载它,但是我看不出必须提供.so指向链接时间(-L)或.so自身的路径。
  2. 为什么g++在动态链接时尝试解析符号?没有什么可以阻止我.so在链接时完成操作,而是.so在运行时提供一个不同的(不完整的)消息,这会导致程序在尝试使用未定义的符号时崩溃。

我举了一个可重现的例子:

目录结构:

.
??? main.cpp
??? test
    ??? usertest.cpp
    ??? usertest.h
Run Code Online (Sandbox Code Playgroud)

文件内容:

测试/用户测试

#ifndef USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
#define USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346

namespace usertest
{
    void helloWorld();

    // This method is not defined anywhere
    void byeWorld();
};

#endif /* USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346 */
Run Code Online (Sandbox Code Playgroud)

测试/用户测试.cpp

#include "usertest.h"
#include <iostream>

void usertest::helloWorld()
{
    std::cout << "Hello, world\n";
}
Run Code Online (Sandbox Code Playgroud)

main.cpp

#include "test/usertest.h"

int main()
{
    usertest::helloWorld();
    usertest::byeWorld();
}
Run Code Online (Sandbox Code Playgroud)

用法

$ cd test
$ g++ -c -fPIC usertest.cpp
$ g++ usertest.o -shared -o libusertest.so
$ cd ..
$ g++ main.cpp -L test/ -lusertest
$ LD_LIBRARY_PATH="test" ./a.out
Run Code Online (Sandbox Code Playgroud)

预期行为

我希望所有尝试启动时都崩溃,a.out因为它无法在中找到必要的符号libusertest.so

实际行为

建立a.out链接时失败,因为它找不到byeWorld()

/tmp/ccVNcRRY.o: In function `main':
main.cpp:(.text+0xa): undefined reference to `usertest::byeWorld()'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

rus*_*tyx 5

使用ELF格式,实际上不必知道哪个符号属于哪个库,因为在执行程序时会发生实际的符号解析。尽管按照惯例ld,在生成二进制文件时仍会解析符号。这是为了您的方便,以便在缺少符号时立即获得反馈,因为在这种情况下,您的程序将无法运行。

使用该--warn-unresolved-symbols标志,可以将ld这种情况下的行为从错误更改为警告:

$ g++ -Wl,--warn-unresolved-symbols main.cpp -lusertest
Run Code Online (Sandbox Code Playgroud)

应该发出警告,但仍创建可执行文件。请注意,您仍然需要提供库名称,否则ld将不知道在哪里寻找所需的符号。

在Windows上,链接器需要确切知道哪个符号属于哪个库,才能生成必要的导入表。因此,不可能用未解析的符号构建PE二进制文件。