Proper C++ Visibility Support

C++可见性控制支持 是GCC编译器从4.0版本提供的特性

一、背景

In ordinary C, if you want to limit the visibility of a function or variable to the current file, you apply the static keyword to it. In a shared library containing many files, though, if you want a symbol to be available in several files inside the library, but not available outside the library, hiding that symbol is more difficult. Most linkers provide convenient ways to hide or show all symbols in a module, but if you want to be more selective, it takes a lot more work.

引用自Controlling Symbol Visibility.

在C语言中,可以使用static关键字,用于限制函数或变量的可见性,但只作用于当前文件。而共享库(或动态库)可能包含多个文件,如果需要将某个符号在库内的多文件间可见,隐藏符号会变得比较棘手。许多链接器提供了将一个模块的所有符号进行隐藏或导出的方法,但这却失去了对符号的可见性控制。

二、目的

Put simply, it hides most of the ELF symbols which would have previously (and unnecessarily) been public.

主要用于隐藏某些ELF符号,而这些符号只有在之前某时段需要甚至完全不需要Be pulibc.

ELF说明:

ELF(Executable Linkable Format)是UNIX类操作系统中普遍采用的目标文件格式文件包含了代码、数据,而有些更包含了符号表、调试信息、行号信息、字符串等。

目标形式:
目标文件有三种类型:

  1. 可重定位文件(Relocatable File) 包含适合于与其他目标文件链接来创建可执行文件或者共享目标文件的代码和数据。 (Linux的*.o 文件 Windows的 *.obj文件)
  2. 可执行文件(Executable File) 包含适合于执行的一个程序,此文件规定了 exec() 如何创建一个程序的进程映像。(比如/bin/bash文件;Windows的*.exe)
  3. 共享目标文件(Shared Object File) 包含可在两种上下文中链接的代码和数据。首先链接编辑器可以将它和其它可重定位文件和共享目标文件一起处理,生成另外一个目标文件。其次,动态链接器(Dynamic Linker)可能将它与某个可执行文件以及其它共享目标一起组合,创建进程映像。
    目标文件全部是程序的二进制表示,目的是直接在某种处理器上直接执行(Linux的.so,如/lib/ glibc-2.5.so;Windows的DLL)

三、优点

It very substantially improves load times of your DSO (Dynamic Shared Object). For example, a huge C++ template-based library which was tested (the TnFOX Boost.Python bindings library) now loads in eight seconds rather than over six minutes!

可减少动态共享库的载入时间

It lets the optimiser produce better code. PLT indirections (when a function call or variable access must be looked up via the Global Offset Table such as in PIC code) can be completely avoided, thus substantially avoiding pipeline stalls on modern processors and thus much faster code. Furthermore when most of the symbols are bound locally, they can be safely elided (removed) completely through the entire DSO. This gives greater latitude especially to the inliner which no longer needs to keep an entry point around "just in case".

可完全避免过程链接表(PLT)的间接性访问过程:在PIC代码中的函数调用或者变量的访问需要通过全局偏移表用于物理位置的查找。因此可大幅减少对于处理器的管线停滞,并且,大部分符号是本地绑定的,DSO可进行安全的移除操作。

It reduces the size of your DSO by 5-20%. ELF's exported symbol table format is quite a space hog, giving the complete mangled symbol name which with heavy template usage can average around 1000 bytes. C++ templates spew out a huge amount of symbols and a typical C++ library can easily surpass 30,000 symbols which is around 5-6Mb! Therefore if you cut out the 60-80% of unnecessary symbols, your DSO can be megabytes smaller!
Much lower chance of symbol collision. The old woe of two libraries internally using the same symbol for different things is finally behind us with this patch. Hallelujah!

减少动态库大小。由于ELF导入的符号表格式中的完全独立的标识使用了重量级的模板,导致符号表格式所占空间甚大。基于此,减少不必要的全局符号将大大缩减动态共享库,并且调用者的解析成本随之而降;与此同时,还将大幅度降低符号冲突的几率。哈利路亚!!

四、使用

GCC4.0提供了-fvisibility编译选项,其可选取值为default或者hidden:前者表示,编译对象中对于没有显式地设置为隐藏的符号,其属性均表示对外可见;后者则将隐藏没有进行显式设置的符号。对于编译没有显式指定的情况,则GCC编译前将使用-fvisibility=default,表示库的符号均对外可见。

针对于编译选项,GCC使用优先级更高的attribute机制用于设置符号的可见性属性,其设置对象包括变量、函数、模板、C++类等。所以,一般在共享库,尤其是大型的动态库中,编译使用-fvisibility=default用于进行全局符号的隐藏设置,将需要向外展示的符号则用attribute((visibility("default"))进行覆盖性的设置。

代码示例:

int a(int n) {return n;}  
   
__attribute__((visibility("hidden"))) int b(int n) {return n;}  
   
__attribute__((visibility("default"))) int c(int n) {return n;}  
   
class X  
{  
    public:  
        virtual ~X();  
};  
   
class __attribute__((visibility("hidden"))) Y  
{  
    public:  
        virtual ~Y();  
};  
   
class __attribute__((visibility("default"))) Z  
{  
    public:  
        virtual ~Z();  
};  
   
X::~X() { }  
Y::~Y() { }  
Z::~Z() { }  

五、批量设置--pragmas

Another way to mark symbols as default or hidden is with a new pragma in GCC 4.0. The GCC visibility pragma has the advantage of being able to mark a block of functions quickly, without the need to apply the visibility attribute to each one.

GCC还提供了批量设置的方法,即使用pragmas关键字,进行快速的块符号标记。

void f() { }  
   
# pragma GCC visibility push(default)  
void g() { }  
void h() { }  
# pragma GCC visibility pop

六、符号可见性设置的场景

If your library exports a C++ interface, the symbols associated with that interface must be visible.

如果导出某c++接口,与其关联的符号必须设置为可见。

If your symbol uses runtime type identification (RTTI) information, exceptions, or dynamic casts for an object that is defined in another library, your symbol must be visible if it expects to handle requests initiated by the other library. For example, if you define a catch handler for a type in the C++ standard library, and you want to catch exceptions of that type thrown by the C++ standard library, you must make sure that your typeinfo object is visible.

如果某一符号使用了定义于其他库的对象信息,RTTI,异常,动态转换等,那么在希望处理由其他库发起的请求的时候,该符号必须设置为visible。

If you expect the address of an inline function used in different code modules to be the same for each module, the function must be exported from each code module.

如果希望不同模块都使用的某一内嵌函数其地址保持一致,则该函数必须设置为visibile。

If your inline function contains a static object and you expect there to be only one copy of that object, your symbol for that static object must be visible.

如果某一内嵌函数包含了一个静态对象,且希望该对象为单例,则该对象必须设置为visible。

七、引申

关于GCC的attribute机制:

  • Using GNU C attribute
  • Declaring Attributes of Functions

你可能感兴趣的:(Proper C++ Visibility Support)