【问题标题】:C++: What to recompile when an inline function in a dynamic library changes?C++:当动态库中的内联函数发生变化时要重新编译什么?
【发布时间】:2017-09-19 12:46:47
【问题描述】:

我有两个动态库和一个可执行文件:

  • libOtherLibrary.so
    • 这是一个由其他人编写的现有开源库。
  • libMyLibrary.so
    • 这是我自己的库,它依赖于 libOtherLibrary.so
  • exe
    • 这是我自己的可执行文件,它依赖于这两个库。

作为查看何时调用特定函数的测试,我在 libOtherLibrary.so内联函数 中添加了一条打印语句(代码细节无关紧要) :

template<class T>
inline void className<T>::clear() const
{
    Info << "Hello World!" << endl; // My message!
    if (isTmp() && ptr_)
    {
        if (ptr_->unique())
        {
            delete ptr_;
            ptr_ = 0;
        }
        else
        {
            ptr_->operator--();
            ptr_ = 0;
        }
    }
}

然后我重新编译 libOtherLibrary.so,然后重新编译 libMyLibrary.so。最后我重新链接(所以没有重新编译)exe

结果是在 libMyLibrary.so 中发起的对 className&lt;T&gt;::clear() 的任何调用都使用了此内联方法的 实现,而对 className&lt;T&gt;::clear() 的任何调用都是由libOtherLibrary.so 使用了新的实现。

当我决定也重新编译 exe(然后链接它)时,结果是始终使用 new 实现。


我的问题是:谁能向我解释为什么 exe 需要重新编译,而不是仅仅重新链接

libOtherLibrary.so的函数className&lt;T&gt;::clear()的内联应该发生在libMyLibrary.so的编译阶段,不是吗?毕竟,它是 libMyLibrary.so 中包含的调用className&lt;T&gt;::clear() 的函数。然后我希望链接 exe 就足够了,因为 exe 不会调用这个特定的内联函数。单独的链接器将负责任何更改的 ABI 兼容性。

【问题讨论】:

  • 首先,function-templates 隐含为inline。其次,如果您的任何库中没有该 function-templateODR use,则尽管您进行了更改,编译器也不会发出任何新代码。每个翻译单元都会有一份您的function-template 实例化的单独副本。因此,当您不重新编译它时,旧的仍将被使用。现在,拥有两个独立且不同的副本违反了 ODR,但 C++ 不需要编译器来诊断它

标签: c++ gcc dynamic-library recompile inline-functions


【解决方案1】:

我的问题是:有人可以向我解释为什么需要 exe 重新编译,而不是仅重新链接

因为,对于您的特定用例,没有它,您将招致ODR violation 的愤怒。


结果是任何对className&lt;T&gt;::clear() 的调用都在 libMyLibrary.so 使用了这个内联方法的 old 实现,而对 className&lt;T&gt;::clear() 的任何调用都是由 libOtherLibrary.so 使用了新的实现。

当你有一个 function-template 说:

template<class T>
inline void className<T>::clear(){
    ....
}

在多个翻译单元(.cpp 文件)中是ODR used。它的实例化将在每个这样的翻译单元中定义,因为 function-templates 隐含inline

这种多重定义的规则在basic.def.odr/6 中说明。列出的要求之一是“D 的每个定义都应包含相同的标记序列;”

修改该函数模板并重新编译一些使用 ODR 的翻译单元,并链接您的程序,而不重新编译 所有使用 ODR 的翻译单元违反了 C++ 的神圣单一定义规则.

诊断它不需要编译器工具链。

【讨论】:

  • 谢谢。 --- 只是为了确保我完全理解你:libMyLibrary.so 确实为 tmp 实例化了它自己的函数模板。 exe 还使用与 libMyLibrary.so 相同的类型名 T 来实例化函数模板。 --- 因此,对于这种情况,您要说的是,对于特定的T,将有两个className&lt;T&gt; 的定义。然后,由于 exe 被执行,它的定义首先出现,因此它的定义被使用。 --- 但是,libOtherLibrary.so 的调用使用新定义。为什么?因为 className 在 libOtherLibrary.so?
  • @KevinvanAs ODR 违规的后果尚未明确。事实上,ODR 违规将调用可怕的Undefined Behavior。链接的 C++ 文档的最后一段说 “如果 D 的定义不满足这些要求,则行为未定义。”.
  • @KevinvanAs,要回答您的问题,UB 不是我们可以解释的,但是,最有可能的情况是所有定义都内联在翻译单元中,并且它们引用了自己的定义复制,(虽然不太可能)链接器可以假设您没有违反 ODR,并在尝试合并定义时搞砸了。
【解决方案2】:

另一种说法:

假设编译器确实 inlining(与inline 关键字无关,例如GCC 如果您编译并使用@ 链接 会努力内联987654324@ 即使是 标记为inline 的函数),只要内联函数的定义发生变化,就应该重新编译代码。在大多数优秀的 C++ 程序中,该定义出现在一些 header 文件中(明确包含 inline 函数)。

所以当头文件改变时你应该重新编译。良好的构建自动化工具(例如 makeg++ -MD 结合使用)可以解决这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-01
    • 1970-01-01
    相关资源
    最近更新 更多