控制符号的可见性
在普通的C语言中,如果您希望将函数或者变量限制在当前文件中,需要对其使用static关键字。然而,在一个包含很多文件的共享库中,如果您希望某个符号可以被共享库内部的几个文件访问,而又不提供给外部,则对符号进行隐藏处理就会比较困难。大多数的连接器都提供一些便利的方法来隐藏或者显示模块中所有的符号,但如果希望更加具有选择性,则需要更多的处理。
在Mac OS X v10.4之前,有两种机制可以控制符号的可见性。
第一种技术是通过__private_extern__关键字,将符号声明为共享库私有,而又可以从当前文件输出。这个关键字可以使用到static或extern关键字可以使用的任何地方;
第二种技术是使用输出列表。输出列表是一个文件,含有您希望隐藏或者显示的符号名称。C语言的符号名称比较容易确定(在名称前面加一个下划线字符就可以),C++的符号名称的确定则要复杂得多。由于有类和名字空间,编译器必须包含更多的信息才能唯一标识每一个符号,编译器也因此为每个符号创建一个所谓的重整名称(mangled name)。这种重整名称的生成规则通常和编译器有关,难于进行逻辑的推断,也很难在共享库定义的大列表中找到。
幸运的是,GCC 4.0提供了一些新的、用于改变符号可见性的方法。下面部分将对这些新方法进行描述:
一、使用GCC 4.0来标识符号的可见性
从Mac OS X v10.4开始,要隐藏C++符号名称就简单很多了。GCC 4.0编译器支持一些用于隐藏和显示符号名称的新选项,同时还支持一个新的pragma和一些编译器属性,用于改变代码符号的可见性。
请注意:下面的特性只在GCC 4.0和更新的版本提供。
请注意:default设置不是指编译器缺省的处理方式。和hidden设置一样,default来自ELF格式定义的可见性名称。具有default可见性的符号和所有不使用特殊机制的符号具有相同的可见性类型&8212;也就是说,它将作为公共接口的一部分输出。
编译器还支持-fvisibility-inlines-hidden选项,用于强制隐藏所有的嵌入函数。当您希望对大多数项目使用缺省的可见性,但又希望隐藏所有的嵌入函数时,您可能会用到这个选项。
如果用GCC 4.0编译代码,可以用可见性属性将个别的符号标识为default或hidden:
可见性属性会覆盖编译时通过-fvisibility选项指定的值。因此,增加default可见性属性会使符号在所有情况下都被输出,反过来,增加hidden可见性属性会隐藏相应的符号。
可见性属性可以应用到函数,变量,模板,以及C++类。如果一个类被标识为hidden,则该类的所有成员函数,静态成员变量,以及编译器生成的元数据,比如虚函数表和RTTI信息也都会被隐藏。
请注意:虽然模板声明可以用可见性属性来标识,但是模板实例则不能。这是个已知的限制,在GCC的未来版本中可能被修复。
为了演示这些属性如何在编译时进行工作,请看一下下面的声明:
从实践上看,用可见性属性标识符号的可见或者隐藏比起用__private_extern__关键字隐藏个别的符号要好。__private_extern__关键字采用的方法是缺省暴露所有的符号,然后选择性地隐藏所有的符号。在大型的共享库中,相反的方法通常更好一些。因此,隐藏所有的符号,然后有选择地暴露希望客户使用的符号通常会好一些。
为了简化将符号标识为输出的任务,您可能希望将default的可见性属性集合定义为宏,如下面的例子所示:
二、限制符号可见性的原因
从动态共享库中尽可能少地输出符号是一个好的实践经验。输出一个受限制的符号会提高程序的模块性,并隐藏实现的细节。在库中减少符号的数目还可以减少库的内存印迹,减少动态连接器的工作量。动态连接器装载和识别的符号越少,程序启动和运行的速度就越快。
三、嵌入函数的可见性
嵌入函数通常在调用现场被展开,因此在目标文件中完全不表示为符号。然而在很多情况下,由于一些很好的理由,编译器可能会表示出嵌入函数的函数体,并因此为其生成符号。最为常见的情况是如果所有的优化都被禁止了,编译器可能决定不进行嵌入优化;较为少见的情况是函数太大,不能进行嵌入,或者函数的地址在其它地方被使用,因此需要有一个符号。
虽然在C++中,您可以将可见性属性(参见“可见性属性”部分)应用到嵌入函数中,就如同其它符号,但是隐藏所有的嵌入函数通常要更好一些。从动态共享库中输出嵌入函数会带来一些复杂的问题。因为有几个因素跟编译器的选择(是将函数表示出来,还是进行嵌入处理)有关系,和共享库的不同连编版本一起连编客户程序的时候,可能会碰到错误。
C和 C++的嵌入函数语法有一些细微的区别,记住这一点也是很重要的。在C程序中,只有一个源代码文件可以为一个嵌入函数提供out-of-line(译者注:与inline相对应,指不进行嵌入展开)的定义。这意味着C程序员对out-of-line的拷贝驻留的位置有精确的控制。因此对于基于C的动态共享库来说,只输出嵌入函数的一个拷贝是可能的。对于C++,嵌入函数的定义必须包含在每个使用该函数的翻译单元中。因此如果编译器表示了一个out- of-line的拷贝,则可能会有几个拷贝驻留在不同的翻译单元中。
最后,如果希望隐藏所有的嵌入函数(但不希望隐藏所有的其它代码),可以用在代码编译的时候使用-fvisibility-inlines-hidden选项。如果您已经向编译器传递了-fvisibility=hidden选项,则没有必要使用-fvisibility-inlines-hidden选项。
四、符号可见性和Objective-C
Objective- C是C的限制超集, Objective-C++则是C++的限制超集。这意味着这里关于C和C++符号可见性的所有讨论都同样可以应用到Objective-C和 Objective-C++上。您可以用编译器选项,可见性属性,以及可见性pragma来隐藏Objective-C代码文件中的C和C++代码。然而,这些可见性控制只能应用到代码中的C或者C++子集,不能应用到Objective-C的类和方法上。
Objective-C类和消息名称由Objective-C运行环境来限制,而不是通过连接器,因此可见性的说明对它们是不起作用的。我们无法在共享库的客户程序中隐藏共享库定义的Objective-C类。
第一种技术是通过__private_extern__关键字,将符号声明为共享库私有,而又可以从当前文件输出。这个关键字可以使用到static或extern关键字可以使用的任何地方;
第二种技术是使用输出列表。输出列表是一个文件,含有您希望隐藏或者显示的符号名称。C语言的符号名称比较容易确定(在名称前面加一个下划线字符就可以),C++的符号名称的确定则要复杂得多。由于有类和名字空间,编译器必须包含更多的信息才能唯一标识每一个符号,编译器也因此为每个符号创建一个所谓的重整名称(mangled name)。这种重整名称的生成规则通常和编译器有关,难于进行逻辑的推断,也很难在共享库定义的大列表中找到。
幸运的是,GCC 4.0提供了一些新的、用于改变符号可见性的方法。下面部分将对这些新方法进行描述:
一、使用GCC 4.0来标识符号的可见性
从Mac OS X v10.4开始,要隐藏C++符号名称就简单很多了。GCC 4.0编译器支持一些用于隐藏和显示符号名称的新选项,同时还支持一个新的pragma和一些编译器属性,用于改变代码符号的可见性。
请注意:下面的特性只在GCC 4.0和更新的版本提供。
- 编译器选项
请注意:default设置不是指编译器缺省的处理方式。和hidden设置一样,default来自ELF格式定义的可见性名称。具有default可见性的符号和所有不使用特殊机制的符号具有相同的可见性类型&8212;也就是说,它将作为公共接口的一部分输出。
编译器还支持-fvisibility-inlines-hidden选项,用于强制隐藏所有的嵌入函数。当您希望对大多数项目使用缺省的可见性,但又希望隐藏所有的嵌入函数时,您可能会用到这个选项。
- 可见性属性
如果用GCC 4.0编译代码,可以用可见性属性将个别的符号标识为default或hidden:
__attribute__((visibility("default"))) void MyFunction1() {}
__attribute__((visibility("hidden"))) void MyFunction2() {}
可见性属性会覆盖编译时通过-fvisibility选项指定的值。因此,增加default可见性属性会使符号在所有情况下都被输出,反过来,增加hidden可见性属性会隐藏相应的符号。
可见性属性可以应用到函数,变量,模板,以及C++类。如果一个类被标识为hidden,则该类的所有成员函数,静态成员变量,以及编译器生成的元数据,比如虚函数表和RTTI信息也都会被隐藏。
请注意:虽然模板声明可以用可见性属性来标识,但是模板实例则不能。这是个已知的限制,在GCC的未来版本中可能被修复。
为了演示这些属性如何在编译时进行工作,请看一下下面的声明:
int a(int n) {return n;}用-fvisibility=default选项编译这段代码会使函数a和c ,还有类X和Z的符号输出到库的外部。而如果用-fvisibility=hidden选项进行编译,则会输出函数c和类Z的符号。
__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() { }
从实践上看,用可见性属性标识符号的可见或者隐藏比起用__private_extern__关键字隐藏个别的符号要好。__private_extern__关键字采用的方法是缺省暴露所有的符号,然后选择性地隐藏所有的符号。在大型的共享库中,相反的方法通常更好一些。因此,隐藏所有的符号,然后有选择地暴露希望客户使用的符号通常会好一些。
为了简化将符号标识为输出的任务,您可能希望将default的可见性属性集合定义为宏,如下面的例子所示:
#define EXPORT __attribute__((visibility("default")))使用宏的优点是当您的代码也需要在其它平台编译的时候,可以将宏改为适用于其它平台的关键字。
// Always export the following function.
EXPORT int MyFunction1();
- Pragmas
void f() { }在这个例子中,函数g和h被标识为default,因此无论-fvisibility选项如何设置,都会输出;而函数f则遵循-fvisibility选项设置的任何值。push和pop两个关键字标识这个pragma可以被嵌套。
#pragma GCC visibility push(default)
void g() { }
void h() { }
#pragma GCC visibility pop
二、限制符号可见性的原因
从动态共享库中尽可能少地输出符号是一个好的实践经验。输出一个受限制的符号会提高程序的模块性,并隐藏实现的细节。在库中减少符号的数目还可以减少库的内存印迹,减少动态连接器的工作量。动态连接器装载和识别的符号越少,程序启动和运行的速度就越快。
三、嵌入函数的可见性
嵌入函数通常在调用现场被展开,因此在目标文件中完全不表示为符号。然而在很多情况下,由于一些很好的理由,编译器可能会表示出嵌入函数的函数体,并因此为其生成符号。最为常见的情况是如果所有的优化都被禁止了,编译器可能决定不进行嵌入优化;较为少见的情况是函数太大,不能进行嵌入,或者函数的地址在其它地方被使用,因此需要有一个符号。
虽然在C++中,您可以将可见性属性(参见“可见性属性”部分)应用到嵌入函数中,就如同其它符号,但是隐藏所有的嵌入函数通常要更好一些。从动态共享库中输出嵌入函数会带来一些复杂的问题。因为有几个因素跟编译器的选择(是将函数表示出来,还是进行嵌入处理)有关系,和共享库的不同连编版本一起连编客户程序的时候,可能会碰到错误。
C和 C++的嵌入函数语法有一些细微的区别,记住这一点也是很重要的。在C程序中,只有一个源代码文件可以为一个嵌入函数提供out-of-line(译者注:与inline相对应,指不进行嵌入展开)的定义。这意味着C程序员对out-of-line的拷贝驻留的位置有精确的控制。因此对于基于C的动态共享库来说,只输出嵌入函数的一个拷贝是可能的。对于C++,嵌入函数的定义必须包含在每个使用该函数的翻译单元中。因此如果编译器表示了一个out- of-line的拷贝,则可能会有几个拷贝驻留在不同的翻译单元中。
最后,如果希望隐藏所有的嵌入函数(但不希望隐藏所有的其它代码),可以用在代码编译的时候使用-fvisibility-inlines-hidden选项。如果您已经向编译器传递了-fvisibility=hidden选项,则没有必要使用-fvisibility-inlines-hidden选项。
四、符号可见性和Objective-C
Objective- C是C的限制超集, Objective-C++则是C++的限制超集。这意味着这里关于C和C++符号可见性的所有讨论都同样可以应用到Objective-C和 Objective-C++上。您可以用编译器选项,可见性属性,以及可见性pragma来隐藏Objective-C代码文件中的C和C++代码。然而,这些可见性控制只能应用到代码中的C或者C++子集,不能应用到Objective-C的类和方法上。
Objective-C类和消息名称由Objective-C运行环境来限制,而不是通过连接器,因此可见性的说明对它们是不起作用的。我们无法在共享库的客户程序中隐藏共享库定义的Objective-C类。
发表评论