“-fno-semantic-interposition”和“-fvisibility = protected”有什么确切的区别?

5
https://gcc.gnu.org/onlinedocs/gcc-8.1.0/gcc/Optimize-Options.html#index-fsemantic-interposition
-fsemantic-interposition
某些目标文件格式,如ELF,允许动态链接器通过交叉引用符号来进行重定位。这意味着对于从DSO导出的符号,编译器无法执行过程间传播、内联和其他优化,因为涉及到的函数或变量可能会发生更改。虽然此功能非常有用,例如通过调试实现重写内存分配函数,但在代码质量方面是昂贵的。通过-fno-semantic-interposition,编译器假定如果函数被交叉引用,则覆盖函数将具有完全相同的语义(和副作用)。类似地,如果变量被交叉引用,则变量的构造函数也将是相同的。该标志对于显式声明为内联的函数(其中永远不允许交叉引用改变语义)和显式声明为弱的符号没有影响。
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-visibility-function-attribute: protected protected可见性类似于默认可见性,但它表示定义模块内的引用与该模块中的定义绑定。也就是说,声明的实体不能被另一个模块覆盖。
这听起来完全一样。
除了将任何显式标记为default的函数更改为protected之外,是否有任何真正的区别?

如果您已经使用了-fvisibility=hidden并注释了函数的可见性(有关更多详细信息,请参见此处),那么语义层面的插入确实没有太大用处。 - yugr
顺便提一下,通常不建议使用protected可见性,因为它会减慢运行时链接速度(请参见Ian Lance Taylor的这篇优秀文章)。 - yugr
@yugr,间接引用仅在需要地址的罕见情况下发生,并且据我所知,它仍然不比“默认”更糟。 - o11c
2个回答

1
这个问题很难回答,因为目前 -fsemantic-interposition 实际上并没有起作用。在这个例子中,
int a;

int
f1 (int a)
{
  return a;
}

int
f2 (void)
{
  return f1 (a) - a;
}
f2的主体已经被有效地优化为return 0;,即使使用-O2 -fsemantic-interposition(在文档中选项的意义被颠倒的情况下,也适用于-O2 -fno-semantic-interposition)。我提交了一个错误报告。 可能的意图是-fsemantic-interposition将禁用这种优化,以便您最终调用f1并显式计算结果。
ELF符号可见性与此实际上没有关系,主要是因为它仅适用于动态链接。链接器仍然可以在静态链接时交换符号(例如使用-z muldefs选项),因此编译器应该真正提供一种启用交换而不改变符号属性的方法(除了__attribute__ ((weak))之外的其他东西)。

根据那个 bug 的最后评论,我添加了 -fPIC ... 然后 -fsemantic-interposition -fvisibility=protected 允许与 -fno-semantic-interposition 完全相同的优化。 - o11c
1
-fsemantic-interposition 只影响使用 -fPIC 编译的共享库代码。可执行文件中的函数是不可互换的,因此编译器将优化它们,而不管 -semantic-interposition。由于这个原因,缺陷被判定为无效。 - yugr

0

-fno-semantic-interposition 应该是关于代码生成的。 -fvisibility=protected 是关于符号属性的。在 -fsemantic-interposition 之前就有了 protected 的可见性,正如 Ulrich Drepper 在How to Write Shared Libraries中所解释的那样,这是一个你不应该使用的可见性属性。

通用的 ELF ABI 定义了另一种可见性模式:protected。在这个方案中,对于同一对象中定义的符号的引用总是在本地满足。但是这些符号仍然可以在 DSO 外部使用。这听起来像是通过避免使用导出符号(参见第 2.2.7 节)来优化 DSO 的理想机制,但实际上并不是。处理对 protected 符号的引用比普通查找更加昂贵。问题在于 ISO C 标准中的一个要求。标准要求指向同一函数的函数指针可以进行相等比较。如果使用快速和简单的实现方式来实现 protected 可见性,则会违反此规则。假设一个应用程序引用了 DSO 中的一个 protected 函数。DSO 中还有另一个函数引用了该函数。应用程序中的指针指向应用程序 PLT 中的函数条目。如果 protected 符号查找只是返回 DSO 内部函数的地址,则地址将不同。在没有此要求的编程环境中,使用 protected 可见性将是有用且快速的。但由于系统上通常只有一个动态链接器的实现,并且该实现也必须处理 C 程序,因此强烈不建议使用 protected。

如果您希望库引用自己的全局变量而不可能被干扰(并且由于避免了PLT,函数速度更快),则首选方法是使用隐藏别名定义符号,并在内部使用隐藏别名。


这并没有解释-fno-semantic-interposition有什么不同。(也没有解释受保护的可见性为什么比默认可见性更糟糕...它忽略了大多数函数是直接调用而不是转换成函数指针的事实) - o11c
-fno-semantic-interposition 告诉编译器优化代码,就像符号不可交换一样。 __attribute__((visibility("protected"))) 告诉编译器将一个可见性属性附加到生成的符号上(-fvisibility=* 命令行选项指定了默认值),从而指示连接器以特定的方式链接符号。也就是说,对于可见的函数,通过 PLT 链接;对于隐藏的函数,通过 IP 相对链接;对于受保护的函数,则通过涉及 PLT 的某种昂贵但不明确的方式链接。 - Petr Skocik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接