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

Ebr*_*owi 3 c c++ linker gcc ld

我要问我的理解是否正确。

inline这是对C ++编译器的建议,建议在看起来更好时将其替换为函数,因此从库外部调用标记为内联的过程应该是不可靠的,并且在逻辑上应默认隐藏它们,以防止其他人将其作为对它们的更新来调用编译器或代码库可以更改决策(从而删除inlined函数和ABI损坏?)。

但是,这似乎不是默认设置,-fvisibility-inlines-hidden应该进行设置以使其实现。我在这里问为什么会这样?设置不具有任何实际用例,仅由于遗留原因而存在吗?

Sta*_*irl 5

他们逻辑上应该默认隐藏

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

内联函数应该具有公共可见性,以便动态链接程序可以在运行时从所有现有定义中选择一个定义。

GCC Wiki提到了这一点:

-fvisibility-inlines-hidden可以在没有源更改的情况下使用,除非您需要用内联覆盖它否则内联地址对于函数本身或任何函数本地静态数据都很重要


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

// 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);
}
Run Code Online (Sandbox Code Playgroud)

共享对象源:

#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);
}
Run Code Online (Sandbox Code Playgroud)

如果您同时构建了两者并针对此共享库链接可执行文件,则可以看到foo总是在两个翻译单元中返回相同的地址。

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

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


如今,大多数人认为这inline与实际的函数内联无关。这不是真的。ELF目标试图使动态链接透明化,例如,如果程序作为单个可执行文件或多个共享库构建,则程序应具有相同的行为。

为了使ELF成为可能,需要通过GOT或通过PLT调用具有公共可见性的所有功能,就好像它是“导入”功能一样。这是必需的,以便每个函数都可以被另一个库(或可执行文件本身)覆盖。这也禁止对所有公共非内联函数进行内联(请参阅此处的 3.5.5节该节显示了PIC中的公共函数调用应通过PLT进行)。

完全有可能进行公共内联函数的代码内联,因为inline当内联函数的多个定义不相等时,允许未定义程序行为。

有趣的是:Clang违反了ELF要求,并且无论如何都可以在ELF目标上内联公共功能。GCC可以对-fno-semantic-interpositionflag 做同样的事情。

  • 不它不是。可以对内联函数进行内联(在这种情况下,该实例没有地址),但是采用内联函数的地址应始终可以得到相同的结果。 (2认同)
  • @UKMonkey您从未构建“纯C ++程序”,而是在构建带有ELF可执行格式的POSIX系统的C ++程序。ELF要求通过PLT调用公共功能。一些编译器(Clang)选择忽略此要求,另一些编译器(GCC)选择遵循此要求。如果要编写C ++程序,则必须了解这些内容。C ++标准不是万能的,而tbh在确定应如何构建程序方面的能力最低。如有必要,POSIX等标准可以扩展和覆盖C ++标准。 (2认同)