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

时间:2018-02-05 11:08:49

标签: c++ c gcc linker ld

我想知道我的理解是否正确。

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

然而,似乎这不是默认设置,应设置-fvisibility-inlines-hidden以实现此目的。我问这里为什么会这样?设置是否有任何真实的用例,它只是因为遗留的原因?

3 个答案:

答案 0 :(得分:4)

  

默认情况下,它们应默认隐藏

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);
}

共享对象来源:

#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);
}

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

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

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

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

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

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

有趣的说明:Clang违反了ELF要求,无论如何都能够在ELF目标上内联公共功能。 GCC可以使用-fno-semantic-interposition标志执行相同操作。

答案 1 :(得分:2)

  

inline是C ++编译器的建议,用于在函数看到更好的时候替换函数

没有。最初的情况可能是90年代后期,但很长一段时间都不是这样。

请参阅this answer以获得更好的解释。

  

因此,从库外部调用标记为内联的过程不应该是可靠的

  1. 你的初步假设已经错了,所以因此正在从错误的前提出发
  2. 即使编译器 内联一个调用(可以使用或不使用inline关键字),也可以在特定的调用站点< / em>的。内联不是一个函数发生的事情,它必须像往常一样发出,而是发送到函数调用

    编译器完全有可能内联一些函数而不是其他函数,这取决于它对在调用站点产生最佳代码的看法。

  3. 现在,您不清楚您认为inline在图书馆中出现了什么问题,因此很难直接解决这个问题。

答案 2 :(得分:1)

  

inline是C ++编译器的一个建议,用于在函数看到更好时替换函数,因此调用标记为从库外部内联的过程应该不可靠,并且它们在逻辑上应该默认隐藏

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

此外,标准有点要求&foo在程序中的任何地方都是相同的地址,尽管标准没有提及DSO / DLL的任何内容,因此编译器在这些方面有一些自由(事实上,MSVC遵循相反的方法,默认情况下隐藏所有内容。)

  

然而,似乎这不是默认设置,并且应设置-fvisibility-inlines-hidden以使其发生。

尽管有名称,-fvisibility-inlines-hidden仅影响内联类成员函数。对于其他所有内容,-fvisibility=hidden就足够了。

  

没有设置任何真实用例,并且只是因为遗留原因而存在?

是。 IIRC旗帜在GCC 3.2中实施(或足够接近);将其设为默认会破坏许多遗留代码。