2015-04-23 wcdj
摘要:在apple的文档中发现一篇关于符号可见性介绍的好文《Controlling Symbol Visibility》,同样适用于C/C++,在学习的过程中对关键部分顺带翻译下。
Controlling Symbol Visibility
控制符号的可见性
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.
在C语言中,可以使用static关键字限制符号(函数或变量)只对当前的文件可见,即,static对符号的限制在单个文件级别。而共享库(或动态库)可能包含一个或多个文件,如何将符号限制在库文件(模块)的级别,大多数链接器提供了将一个模块的所有符号进行隐藏或导出的方法,但这样对符号的控制会缺乏灵活性,因此,还有一些额外的工作需要我们来处理。
Using GCC 4.0 to Mark Symbol Visibility
The GCC 4.0 compiler supports new options for hiding or showing symbols and also supports a new pragma and compiler attributes for changing the visibility of symbols in your code.
从GCC 4.0 开始, 编译器提供了新的选项,用于隐藏或导出符号。并且提供了新的 pragma 和编译属性来控制符号的可见性。关于动态库的更多话题可以参考另一篇文章《Dynamic Library Programming Topics》。
Compiler Flags
GCC 4.0 supports a new flag for setting the default visibility of symbols in a file. The -fvisibility=
vis compiler option lets you set the visibility for symbols in the current compilation. The value for this flag can be either default
or hidden
. When set to default
, symbols not explicitly marked as hidden are made visible. When set to hidden
, symbols not explicitly marked as visible are hidden. If you do not specify the -fvisibility
flag during compilation, the compiler assumes default
visibility.
GCC 4.0 提供了一个新的编译选项,用于设置符号在一个文件中默认的可见性。即,-fvisibility=vis 编译选项,且此值可以取 default 或者 hidden。当设置为 default 时,对于没有显式设置为隐藏的符号则全部设置为导出(visible);当设置为 hidden 时,对于没有显式设置为导出的符号则全部设置为隐藏(hidden)。如果在编译期间没有指定 -fvisibility 选项,则编译器默认使用 -fvisibility=default ,即,符号全部导出(visible)。
Note: The name default
does not refer to compiler defaults. Like the name hidden
, it comes from visibility names defined by the ELF format. A symbol with default
visibility has the kind of visibility that all symbols do if no special mechanisms are used—that is, it is exported as part of the public interface.
The compiler also supports the -fvisibility-inlines-hidden
flag for forcing all inline functions to be hidden. You might use this flag in situations where you want to use default visibility for most items but still want to hide all inline functions. For more information why this might be necessary for inline functions, see Visibility of Inline Functions.
Visibility Attributes
If you are compiling your code with GCC 4.0, you can mark individual symbols as default or hidden using the visibility attribute:
__attribute__((visibility("default"))) void MyFunction1() {} |
__attribute__((visibility("hidden"))) void MyFunction2() {} |
Visibility attributes override the value specified with the -fvisibility
flag at compile-time. Thus, adding the default
visibility attribute causes a symbol to be exported in all cases, whereas adding the hidden
visibility attribute hides it.
Visibility attributes may be applied to functions, variables, templates, and C++ classes. If a class is marked as hidden, all of its member functions, static member variables, and compiler-generated metadata, such as virtual function tables and RTTI information, are also hidden.
显式指定符号visibility属性的优先级,要高于编译时的 -fvisibility 选项。因此,显式设置符号为 default 属性时,意味着此符号一定是导出的(visible)。 visibility属性可以用来设置,函数、变量、模板、C++类。如果一个C++类的visibility属性被设置为 hidden,那么它的成员函数、静态成员变量、编译时期产生的元数据(比如,虚函数表、RTTI信息)也都会被设置为 hidden 属性。
Note: Although template declarations can be marked with the visibility attribute, template instantiations cannot. This is a known limitation and may be fixed in a future version of GCC.
To demonstrate how these attributes work at compile-time, take a look at the following declarations:
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() { }
Compiling this code with the
-fvisibility=default
flag would cause the symbols for functions
a
and
c
and classes
X
and
Z
to be exported by the library. Compiling this code with the
-fvisibility=hidden
flag would cause the symbols for the function
c
and the class
Z
to be exported.
In a large shared library, the reverse approach is usually better. Thus, it is usually better to hide all symbols and then selectively expose the ones you want clients to use.
因此,建议在一个比较大的共享库(动态库)中,编译时使用 -fvisibility=hidden 选项默认隐藏全部符号,而在代码中使用 __attribute__((visibility("default"))) 有选择的显式指定要导出的符号。这样可以极大地减少符号冲突,并且减少动态解析额外符号的成本。
To simplify the task of marking symbols for export, you might also want to define a macro with the default
visibility attribute set, such as in the following example:
#define EXPORT __attribute__((visibility("default")))
// Always export the following function.
EXPORT int MyFunction1();
The advantage of using a macro is that if your code is also compiled on other platforms, you can change the macro to the appropriate keywords for the compilers on the other platforms.
使用宏的好处是,方便在不同的平台上移植。
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. The use of this pragma is as follows:
另一个设置符号可见性的方法是,在 GCC 4.0 中使用新的 pragma 关键字。优点是可以不用单独对每个符号进行设置,而是批量的进行设置。
void f() { }
#pragma GCC visibility push(default)
void g() { }
void h() { }
#pragma GCC visibility pop
In this example, the functions
g
and
h
are marked as default, and are therefore exported regardless of the
-fvisibility
flag, while the function
f
conforms to whatever value is set for the
-fvisibility
flag. As the names
push
and
pop
suggest, this pragma can be nested.
Reasons for Limiting Symbol Visibility
限制符号可见的原因
It is good practice to export as few symbols as possible from your dynamic shared libraries. Exporting a limited set of symbols improves program modularity and hides implementation details. Reducing the number of symbols in your libraries also decreases the footprint of your library and reduces the amount of work that must be done by the dynamic linker. With fewer symbols to load and resolve, the dynamic linker is able to get your program up and running more quickly.
即,尽可能使模块封装的更加内聚,尽可能减少模块间符号的冲突,尽可能减少动态链接时不必要的符号解析。
Reasons for Making Symbols Visible
设置符号可见的原因
Although it is likely that most C++ symbols in your shared library do not need to be visible, there are some situations where you do need to export them:
-
If your library exports a C++ interface, the symbols associated with that interface must be visible.
-
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. -
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.
-
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.