16

我正在尝试在 Linux 和 gcc 上使用 C++ 符号可见性。似乎首选的方法是使用 -fvisibility=hidden,并根据 Visibility gcc wiki 页面(http://gcc.gnu.org/wiki/Visibility)一个接一个地导出使用的符号。我的问题是许多库处理得不好,他们忘记显式导出符号,这是一个严重的问题。在修复了几个 bug 之后,甚至 boost 的某些部分仍然可能受到影响。当然这些错误应该被修复,但在此之前我想使用一种“安全”的方式来隐藏尽可能多的符号。

我想出了一个解决方案:我将所有符号放在命名空间中,并在其上使用符号隐藏属性并导出公共接口,这样只有我的符号会受到影响。

问题是,当我为每个尚未导出的类并在应用程序中用作类字段时针对该库编译某些内容时,我收到了一条警告消息。

namespace MyDSO __attribute__ ((visibility ("hidden"))) {
  struct Foo {
    void bar() __attribute__ ((visibility ("default"))) {}
  };
}

struct Bar {
  MyDSO::Foo foo;
};

int main() {}

警告消息可以在这个小示例中重现,但名称空间当然应该在应用程序中的其他类的库中。

$ gcc-4.7.1 namespace.cpp -o namespace
namespace.cpp:7:8: warning: ‘Bar’ declared with greater visibility than the type of its field ‘Bar::foo’ [-Wattributes]

据我了解符号可见性,隐藏命名空间应该与使用 -fvisibility=hidden 具有非常相似的效果,但使用后者我从未收到类似的警告。我看到当我将 -fvisibility=hidden 传递给应用程序时,应用程序中的类也将被隐藏,所以我不会收到警告。但是,当我不传递该选项时,标头中的任何符号都不会被编译器隐藏,因此我不会再次收到警告。

这个警告信息的建议是什么?这是一个严重的问题吗?在哪些情况下这会导致任何问题?隐藏命名空间与 fvisibility=hidden 有何不同?

4

2 回答 2

23

在我回答您的具体问题之前,我应该向其他阅读者提及,为每个命名空间应用符号可见性属性是 GCC 特定的功能。MSVC 仅支持类、函数和变量的 dllexport,如果您希望代码可移植,则必须在此处匹配 MSVC。正如我最初的 GCC 符号可见性指南(您在 GCC 网站上链接到的那个)指出的那样,MSVC 基于宏的 dllexport 机制可以很容易地重用以在 GCC 上实现类似的功能,因此移植到 MSVC 将为您提供符号可见性处理“免费”。

关于您的具体问题,GCC 正确地警告您。如果外部用户尝试使用公共类型 Bar,他们几乎肯定需要使用 Bar 中的所有内容,包括 Bar::foo。出于完全相同的原因,所有私有成员函数,尽管是私有的,都需要可见。很多人对此感到惊讶,认为私有成员函数符号在定义上任何人都无法访问,但他们忘记了仅仅因为程序员没有访问权限并不意味着编译器不需要使用权。换句话说,私有成员函数对您来说是私有的,而不是编译器。如果它们出现在头文件中,则通常意味着编译器需要访问,即使在匿名命名空间中也是如此(这仅对程序员是匿名的,而不是倾向于使用内容的哈希作为“真实”命名空间名称的编译器)。

隐藏命名空间与 -fvisibility=hidden 的效果截然不同。这是因为 GCC 会在特定类型(例如 vtables、type_info 等)的符号之上和之外喷出许多符号。 -fvisibility=hidden 隐藏您无法通过任何编译器指令方式隐藏的内容,这对于加载两个二进制文件是绝对必要的使用碰撞符号进入同一进程,例如使用不同版本的 Boost 构建的两个共享对象。

我感谢您尝试解决由 ELF 中损坏的符号可见性引起的问题以及损坏的 C++ 二进制文件的后果以及程序员生产力的严重损失。但是您无法修复它们 - 它们是 ELF 本身的错误,它是为 C 而不是 C++ 设计的。如果有什么安慰的话,几个月前我写了一篇关于这个主题的内部 BlackBerry 白皮书,因为 ELF 符号可见性问题对于我们在 BB10 中的问题与对于任何拥有重要 C++ 代码库的大公司一样大。因此,也许您可​​能会看到针对 C++17 提出的一些解决方案,特别是如果 Doug Gregor 的 C++ Modules 实现取得了良好进展。

于 2013-03-25T01:43:01.517 回答
1

您对可见性属性的使用对我来说似乎倒退了;我认为使用 -fvisibility=hidden 并将可见性“默认”添加到库声明命名空间会获得更好的结果,因为库的接口可能具有默认可见性,或者您无法从应用程序中使用它。如果你不想修改库头文件,你可以在你的#includes 周围使用#pragma GCC visibility push/pop。

此外,正如 Niall 所说,将单个成员函数标记为默认值是行不通的,如果它是库接口的一部分,则整个 Foo 类型需要具有默认可见性。

于 2013-04-17T21:21:38.450 回答