【问题标题】:C++ compilation static variable and shared objectsC++编译静态变量和共享对象
【发布时间】:2011-12-09 04:26:46
【问题描述】:

说明:

一个。 X 类包含一个静态私有数据成员 ptr 和静态公共函数成员 getptr()/setptr()。
在 X.cpp 中,ptr 设置为 NULL。

b. libXYZ.so(共享对象)包含类 X 的对象(即 libXYZ.so 包含 X.o)。

c。 libVWX.so(共享对象)包含类 X 的对象(即 libVWX.so 包含 X.o)。

d。可执行文件 a.exe 包含 X.cpp 作为翻译单元的一部分,最后链接到 libXYZ.so、libVWX.so

PS:
1. 任何类都不涉及用户命名空间。
2. 库和可执行文件还包含许多其他类。
3. 没有执行 dlopen()。所有库都在编译时使用 -L 和 -l 标志链接。

问题陈述:

  1. 在编译 a.exe 并将其与其他库(即 libXYZ.so 和 libVWX.so)链接时,我预计会出现链接器错误(相同符号的冲突/出现多次)但没有得到。

  2. 程序执行时 - 在 SUSE 10 Linux 和 HP-UX 11 IA64 中的行为很奇怪。
    在 Linux 中,当执行流被推送到不同库中的所有对象时,效果只注册在 X 的一个副本中。
    在 HPUX 中,当执行流被推送到不同库中的所有对象时,效果会注册在 3 个不同的 X 副本中(2 个属于每个库,1 个用于可执行文件)

PS:我的意思是在运行程序期间,流程确实通过了属于 a.exe、libXYZ.so 和 libVWX.so 的多个对象,这些对象与属于 X 的静态指针交互。

问题:

  • 预期链接器错误是否不正确?由于两个编译器无声地通过编译,因此在我缺少的这种情况下,可能有一个标准规则。如果是这样,请让我知道。
  • 编译器(Linux 中的 gcc 和 HPUX 中的 aCC)如何决定在最终可执行文件中保留多少 X 副本并在这种情况下引用它们。
  • gcc 和 aCC 是否支持任何标志,在这种情况下会向用户发出警告/停止编译?

提前感谢您的帮助。

【问题讨论】:

    标签: c++ static compilation shared-libraries


    【解决方案1】:

    函数是“公共静态”,所以我假设它是“静态”的 OOP 含义(不需要实例),而不是静态的 C 含义(文件静态;编译单元本地)。因此函数是外部的。

    现在在 Linux 中,您有明确的权利来覆盖库符号,无论是使用另一个库还是在可执行文件中。库中的所有外部符号都使用全局偏移表解析,即使是库实际定义的那个。虽然可执行文件中定义的函数通常不会像这样解析,但链接器注意到符号将从库中获取符号表并将对可执行文件定义的引用放在那里。因此,如果您生成它,库将看到可执行文件中定义的符号。

    这是一个显式功能,旨在让您可以执行替换内存分配函数或包装文件系统操作等操作。 HP-UX 可能没有该功能,因此每个库最终都会调用它自己的实现,而任何其他具有未定义符号的对象都会看到其中一个。

    【讨论】:

    • 您的回答看起来最合乎逻辑。所以给了你积分。唯一仍然存在的悬而未决的问题是 - 有没有办法在链接期间找出这类问题 - 链接器会在哪里抛出错误?如果您知道,请告诉我。
    • @kumar_m_kiran:不幸的是,它是特定于系统的。 ELF 链接器将声明两个 .o 文件中的符号之间的冲突,除非这些符号很弱(在 C++ 内联方法和模板实例中很弱,或者您可以使用 attribute)并且不会声明与库中的符号冲突(按设计)。另一方面,Windows 将声明与任何符号冲突,除非用选项覆盖,否则这是致命的,甚至共享库仍将调用它们自己的定义。
    【解决方案2】:

    “extern”符号(这是 c++ 中的默认值)和“共享库 extern”之间存在差异。默认情况下,符号只是“外部”,这意味着一个“链接单元”的范围,例如可执行文件或库。 所以预期的行为是:没有编译器错误,每个模块都使用自己的副本。 这当然会导致在内联编译等情况下出现问题......等等...... 要声明符号“共享库外部”,您必须使用“.def”文件或编译器声明。 例如在 Visual C++ 中,这将是“_declspec(dllexport)”和“_declspec(dllimport)”。 我目前不知道 gcc 的声明,但我相信有人知道 :-)

    【讨论】:

    • 在 Windows 中就是这样(编译器错误部分除外;除非您使用 /force 覆盖,否则它是链接器错误)。但是 Linux (ELF) 的行为不同。它默认导出符号(如果需要,可以显式隐藏它们)并且总是导入它导出的所有符号(嗯,也可以显式覆盖),所以如果多个对象定义相同的符号,它们总是最终都使用相同的符号定义。
    【解决方案3】:

    我不太确定我是否完全理解了这个场景。然而, 在 Linux 下加载动态对象的默认行为(和其他 Unices) 是为了使库中的所有符号都可用,并且只使用 第一次遇到。因此,如果您同时拥有libXYZ.solibVWX.so 包含符号X::ourData,这不是错误;如果你加载它们 该命令,libVWX.so 将使用来自libXYZ.soX::ourData, 而不是它自己的。从逻辑上讲,这很像模板定义 在标头中:编译器或多或少地随机选择一个,如果 任何定义都与其他定义不同,它是 未定义的行为。这种行为可以 通过将标志 RTLD_LOCAL 传递给 dlopen 来覆盖。

    关于您的问题:

    • 链接器只是实现dlopen 的默认行为(当系统隐式加载库时会得到该行为)。因此,没有错误(但如果任何定义不同,则未定义行为的逻辑等价物)。

    • 编译器无法决定。在加载.so 时做出决定,具体取决于您在调用dlopen 时是指定RTLD_GLOBAL 还是RTLD_LOCAL。当运行时隐式调用dlopen 来解析依赖项时,如果在加载主可执行文件时发生这种情况,它将使用RTLD_GLOBAL,以及当依赖项来自库时用于加载库的内容。 (当然,这意味着RTLD_GLOBAL 将一直传播,直到您显式调用dlopen。)

    【讨论】:

    • 感谢您的初步回复,但是,“因此,如果您同时使用 libXYZ.so 和 libVWX.so .....”并没有解释为什么 Linux 和 HPUX 中的行为不同。在 Linux 中,它符合您的解释。但在 HP 中,看起来 mySO::X::ourData 用于设置 X 的值。
    • @kumar_m_kiran 我不熟悉 HP-UX,所以我不能说。 Posix 确实说过,如果既没有给出RTLD_GLOBAL 也没有给出RTLD_LOCAL,则默认是实现定义的。我描述的行为对应于我所知道的两个平台的行为:Linux 和 Solaris。
    猜你喜欢
    • 2017-08-03
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-31
    • 1970-01-01
    • 2020-02-07
    相关资源
    最近更新 更多