2013-03-22 154 views
11

我正在试验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 =隐藏,但我从来没有使用后者类似的警告。我看到,当我向应用程序传递-fvisibility = hidden时,应用程序中的类也将被隐藏,所以我不会收到警告。但是,当我没有通过选项时,头文件中的任何符号都不会被编译器隐藏,所以我不会再次发出警告。

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

回答

16

在我回答你的具体问题,我应该提到别人读取应用符号可见每个命名空间属性是特定GCC-功能。 MSVC只支持类,函数和变量的dllexport,并且如果你希望你的代码是可移植的,你必须在那里匹配MSVC。正如我原来的GCC符号可视性指南(您在GCC网站上链接的那个指南)所指出的那样,MSVC的基于宏的dllexport机制可以轻松地在GCC上实现类似的功能,因此移植到MSVC将使您免费获得符号可视性处理”。

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

隐藏命名空间已非常-fvisibility不同的效果=隐藏。这是因为GCC在特定类型上喷出了许多符号,例如,对于vtables,对于type_info等。-fvisibility = hidden隐藏你无法通过任何编译器指示的方式隐藏的东西,这对于将两个二进制文件加载到同一个进程中是非常重要的。两个使用不同版本的Boost构建的共享对象。

我非常感谢您的尝试,以解决由于ELF中的符号可见性不足以及破碎的C++二进制文件和很多程序员工作效率下降而导致的问题。但是你不能修复它们 - 它们是ELF本身的缺陷,它是为C而不是C++设计的。如果有任何安慰,我几个月前就此主题撰写了一份内部BlackBerry白皮书,因为ELF符号可见性问题对于我们BB10来说同样适用于任何拥有重要C++代码库的大型公司。因此,也许您可​​能会看到针对C++ 17提出的一些解决方案,特别是如果Doug Gregor的C++ Modules实现取得良好进展。

+0

感谢您的详细解答。只是为了好奇,我对编译器需要哪些不能生成的符号感兴趣?据我所知,在平凡的类甚至可以生成typeinfo。当使用vis = hidden时,即使隐藏了不应该隐藏的符号,也不会收到警告,您只需从链接器获取未定义的符号错误。使用隐藏的命名空间gcc可以检测到问题。也许有合法用途只能导出某个类中的某些符号,但gcc无论如何都会发出警告。 Doug Gregor的C++ Modules非常有趣,我喜欢他的演讲,感谢你分享它。 – VargaD 2013-03-25 17:50:04

+1

简而言之,任何具有虚拟类的类型信息总是被发出,而类型信息只有在typeid()或使用它的东西(异常捕获,dynamic_cast <>等)被使用时才会被发出。此外,大多数编译器为每个程序指定的构造函数发出两个或更多的构造函数实现,并且在析构函数生成中也有一些魔力。总之,-fvisibility = hidden隐藏了很多,你的方法只会隐藏程序员指定的东西而不是魔法内部。大部分这些开始变得更容易与C + +模块,虽然没有解决。 Niall – 2013-03-31 21:43:34

0

您对可见性属性的使用似乎倒退了;我认为使用-fvisibility = hidden可以获得更好的结果,并为库声明命名空间添加可见性“default”,因为库的接口可能具有默认可见性,或者您无法在应用程序中使用它。如果你不想修改库标题,你可以在你的#includes中使用#pragma GCC可见性push/pop。另外,正如Niall所说,将单个成员函数标记为默认值不起作用,如果整个Foo类型是库的接口的一部分,则需要具有默认可见性。

+0

感谢您的回复。我没看见_why_整个班级需要出口。大多数课程甚至没有虚拟表。我有很多测试应用程序,甚至试图抛出未导出的异常,但它们都工作得很好。我试图理解为什么需要导出。你知道它在哪种情况下失败吗?你能举一个例子吗? – VargaD 2013-04-19 20:36:20

+0

基本问题是,在C++中,类具有链接;如果一个课程具有隐藏的可视性,这意味着您不打算在自己的DSO之外使用它。因此,为成员函数提供比其类更大的可见性没有意义,因为您需要使用该类才能使用成员函数。为什么你不想导出类本身? – 2013-05-23 12:48:46

+0

我的问题是,导出整个类不可维护,符号很容易导出。在C中使用-fvisibility隐藏您只需导出您想要导出的符号,但在C++中,您必须导出整个类并难以隐藏您不想导出的所有符号。我发现必须导出具有vtable的类。我多次读过C++中的符号可见性会导致很多问题,并且我不应该隐藏已导出方法的类,但是我看不到为什么。由于可见性,我努力地产生运行时错误,但是我做不到。 – VargaD 2013-05-29 19:33:04

相关问题