符号可见性和命名空间

时间:2013-03-22 00:53:25

标签: c++ gcc visibility symbols

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

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

问题在于,当我针对每个未导出的类编译针对该库的内容时,我收到了警告消息,并且我在应用程序中将其用作类字段。

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?

不同

2 个答案:

答案 0 :(得分:19)

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

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

隐藏命名空间对-fvisibility = hidden的效果非常不同。这是因为GCC会针对特定类型的符号喷出许多符号,例如:对于vtable,对于type_info等.-fvisibility =隐藏隐藏的内容,你不能通过任何编译器指示的方式隐藏,并且它是绝对必要的加载两个二进制文件到相同的进程与冲突的符号,例如使用不同版本的Boost构建的两个共享对象。

我感谢您尝试修复因ELF中符号可见性损坏而导致的问题以及对破坏的C ++二进制文件的影响以及程序员生产力的损失。但是你无法解决它们 - 它们是ELF本身的错误,它是为C而不是C ++设计的。如果有任何安慰,我几个月前在这个主题上写了一篇内部黑莓白皮书,因为ELF符号可见性问题对我们来说在BB10中同样重要,因为它们适用于任何拥有重要C ++代码库的大公司。所以,也许你可能会看到为C ++ 17提出的一些解决方案,特别是如果Doug Gregor的C ++模块实现取得了很好的进展。

答案 1 :(得分:0)

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

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