为什么 `-fvisibility-inlines-hidden` 不是默认值?

Why `-fvisibility-inlines-hidden` is not the default?

我问一下,看看我的理解是否正确。

inline 是对 C++ 编译器的建议,只要它认为更好,就替换一个函数,因此从库外部调用标记为内联的过程不应该可靠,它们在逻辑上应该默认隐藏,防止其他人调用它们作为对编译器或代码库的更新可以改变决定(因此删除 inlined 函数和 ABI 破坏?)。

不过这似乎不是默认设置,应该设置 -fvisibility-inlines-hidden 才能实现。我在这里问为什么会这样?没有任何实际用例的设置,只是因为遗留原因吗?

they logically should be hidden by default

C++ 要求所有函数(包括 inline 函数)在所有翻译单元中具有相同的地址。本地静态信息也应该在所有翻译单元之间共享。如果将程序构建为多个共享对象(.so 文件),则隐藏内联函数会违反这些要求。

内联函数应具有 public 可见性,以便动态 linker 可以在运行时从所有现有定义中选择一个定义。

GCC wiki 提到了这个:

-fvisibility-inlines-hidden can be used with no source alterations, unless you need to override it for inlines where address identity is important either for the function itself or any function local static data.


考虑以下示例。可执行源:

// main.cpp
#include <cstdio>

struct A { };

inline A &foo()
{
    static A a;
    return a;
}

int main()
{
    void *p = &foo();
    std::printf("main() - %p\n", p);
}

共享对象来源:

#include <cstdio>

struct A { };

inline A &foo()
{
    static A a;
    return a;
}

static __attribute__((constructor)) void init()
{
    void *p = &foo();
    std::printf("main() - %p\n", p);
}

如果针对此共享对象同时构建 link 可执行文件,您可以看到 foo 在两个翻译单元中总是 returns 相同的地址。

现在如果你给那些内联函数加上__attribute__((visibility("hidden"))),那么你会看到地址在不同的翻译单元中是不同的。

这不是某些 C++ 程序所期望的。


今天大多数人认为 inline 与实际的函数内联无关。这不完全正确。 ELF 目标试图使动态 linking 透明,例如如果将程序构建为单个可执行文件或多个共享对象,则程序的行为应该相同。

为了使其成为可能,ELF 要求所有具有 public 可见性的函数都可以通过 GOT 或通过 PLT 调用,就好像它是 "imported" 函数一样。这是必需的,以便每个函数都可以被另一个库(或可执行文件本身)覆盖。这也 禁止 内联所有 public 非内联函数(参见第 3.5.5 here 节,其中显示 PIC 中的 public 函数调用应该是通过 PLT 制作)。

public 内联函数的代码内联是可能的,因为 inline 允许在多个内联函数定义不等价时程序行为未定义。

有趣的注意事项:Clang 违反了这个 ELF 要求,并且无论如何都能够在 ELF 目标上内联 public 函数。 GCC 可以用 -fno-semantic-interposition 标志做同样的事情。

inline is a suggestion to C++ compiler for substituting a function whenever it sees better

没有。最初是这样的,大概在 90 年代后期,但很长一段时间都不是这样了。

请参阅 this answer 了解详细说明。

therefore calling a procedure flagged as inlined from outside of a library shouldn't be reliable

  1. 你最初的假设已经错了,所以因此是从一个错误的前提
  2. 出发
  3. 即使编译器确实内联一个调用(它可能使用或不使用 inline 关键字),这是在特定调用站点。内联不是函数发生的事情,它必须始终像往常一样发出,而是发生在函数调用.

    编译器完全有可能内联某些函数调用而不内联其他函数,具体取决于它对什么将在调用站点生成最佳代码的看法。

现在,您认为 inline 导致图书馆出现什么问题尚不清楚,因此很难直接解决。

inline is a suggestion to C++ compiler for substituting a function whenever it sees better, therefore calling a procedure flagged as inlined from outside of a library shouldn't be reliable and they logically should be hidden by default

编译器仍然可以决定内联一些调用并保留其中一些非内联。在这种情况下,您将在链接在一起的所有库中获得内联函数的多个副本。

此外,标准在某种程度上要求 &foo 在您的程序中的任何地方都是相同的地址,尽管该标准没有说明 DSO/DLLs,因此编译器在这些方面有一些自由(事实上,MSVC 采用相反的方法,即默认隐藏所有内容。


However it seems that is not the default setting and -fvisibility-inlines-hidden should be set to make that happen.

尽管名称如此,-fvisibility-inlines-hidden 仅影响内联 class 成员函数。对于其他一切,-fvisibility=hidden 应该足够了。


Does not setting that has any real use case and it is there just because legacy reasons?

是的。 IIRC 该标志是在 GCC 3.2 中实现的(或足够接近);将其设置为默认值会破坏大量遗留代码。