inline
是对C++编译器的建议,当它看到更好的替代函数时,就会将其替换,因此从库外部调用标记为内联的过程可能不可靠,它们应该默认隐藏,以防止其他人调用它们,因为编译器或代码库的更新可能会改变决策(因此移除inline
函数和ABI破坏?)。然而,似乎这不是默认设置,需要设置
-fvisibility-inlines-hidden
才能实现。我在这里问问为什么会这样?不设置有任何真正的用例吗?只是因为遗留原因而存在吗?它们在逻辑上应该默认为隐藏状态。
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
标志做到同样的事情。
-fno-semantic-interposition
标志。 - user1143634
inline
是建议C++编译器在看到更好的函数时替换函数的内容。
不,这最初可能是这样,大约在90年代末,但已经很久没有这种情况了。
请参见此答案以获得良好的解释。
因此,从库外部调用被标记为内联的过程可能不可靠。
即使编译器在有或没有inline
关键字的情况下都可以内联调用(call),这是在特定的调用点完成的。内联不是发生在一个函数上,而是发生在函数调用上。
编译器完全可以内联一些对函数的调用,而忽略其他调用,这取决于编译器对在调用点产生最佳代码的看法。
现在,不清楚你认为inline
在库中引起了什么问题,因此很难直接解决。
inline
视为内联提示,并考虑其主要含义。 - M.Minline
关键字声明的函数,是吗? - Useless&foo
在程序中的任何位置都是相同的地址,尽管标准没有关于DSO/DLLs的说明,所以编译器在这方面有一定的自由(实际上,MSVC遵循相反的方法,即默认隐藏所有内容)。-fvisibility-inlines-hidden
来使其发生作用。-fvisibility-inlines-hidden
仅影响内联类成员函数。对于其他所有内容,-fvisibility=hidden
应该足够了。
inline
的一个关键特性是它是C++代码中避免符号插入开销的标准方式之一。 - user1143634