so符号可见性_控制共享库的符号可见性:第2部分-
IBMXLCC++V13编译器的⾼级知识...
IBM XL C / C ++ V13编译器:可见性属性的⽀持
在 ,我们了解了处理符号可见性的不同⽅法。 最重要的包括:GNU可见性属性扩展,IBMAIX®导出⽂件和GNU版本脚本。 GNU可见性可以使程序员从源代码级别控制符号的可见性,⽽⽤于链接的脚本对于从构建和发布步骤的⾓度来控制符号可见性可能更为有⽤。
但是,随着⽤于AI X和Linux®V13编译器的XL C / C ++的发布,XL C / C ++编译器接受了GNU可见性扩展。 此外,诸如new pragma 指令和new global编译器选项之类的实体也可以⽤于此⽬的。 在这⼀部分中,我们将重点更多放在可见性属性设置及其最终解决⽅案上。此外,我们可能会详细介绍AIX和Linux可见性设计之间的差异以及所实现的AIX可见性的兼容性。
可见性属性说明符
与GNU可见性属性扩展类似,对于XL C / C ++编译器,程序员可以通过属性说明符使⽤GNU属性语法来设置符号可见性。 清单1显⽰了函数和变量声明的⽰例。
清单1.变量和函数的可见性说明符
int __attribute__((visibility("hidden"))) m; // m is set as "hidden"
void __attribute__((visibility("protected"))) fun(); // fun() is set as "protected"
可见性属性的类型可以是default , hidden , protected或internal 。 这与我们在上⼀篇⽂章中介绍的GNU语法⼀致。 但是,在缺省值
中,AIX和Linux实现之间存在细微的差异。 本⽂稍后将对此进⾏讨论。
乍⼀看,可见性属性看起来很简单。 但是,在编程实践中,程序员可能会有更多问题。 例如,⼀个普遍提出的问题是关于具有不同可见性的同⼀符号的多重定义。 这对于处理不同的标头定义可能并不罕见,并且可能给程序员带来很多⿇烦。 从编译器的⾓度来看,此问题是由不完善的现实引起的,解决⽅案将更多地依赖于程序员⽅⾯。 但是,编译器确实提供了规则和便利来提供帮助。 XL C / C ++编译器将采⽤⾸次遇到的可见性属性。 并且,在处理其余部分(具有不同的可见性)时,会向程序员发出警告。 最后,编译器将忽略后者定义的可见性。 清单2提供了⼀个⽰例。
清单2.为同⼀符号指定的不同可见性属性
extern int __attribute__((visibility("hidden"))) m; // "m" is "hidden".
int __attribute__((visibility("protected"))) m; // Compiler warning - "protected" is ignored.
在此⽰例中, m⾸先定义为hidden 。 这意味着在动态链接期间,变量的符号将不可见。 但是,后⾯的代码将再次对其进⾏设置,但是
在protected , 全局可见 。 编译器将发出警告,然后在处理代码时忽略protected属性。
有关可见性的其他⼀些问题将涉及类型 。 在C ++编码中,程序员可能能够⾃⼰创建类型 。 程序员甚⾄可能想知道是否可以为此类类型指定可见性属性。 这是⼀个有趣的问题,因为在C编程中此类问题永远不会发⽣。 所有内置类型都不会⼲扰可见性属性。 但是,在C ++编程中,这不再是正确的。 在下⾯的清单中对此进⾏了演⽰。
gnu编译器清单3.为类指定的可见性属性
class __attribute__((visibility("hidden"))) T // class T has "hidden" visibility
{
public:
static int gVal; // T::gVal is "hidden"
void Dosth(); // T::Dosth() is "hidden"
};
T t1; // t1 is "hidden".
T __attribute__((visibility("internal"))) t2; // t2 has "internal" visibility.
在清单3中,我们定义了具有hidden可见性的类T 在C ++代码中,程序员可以为类以及函数和对象定义可见性属性。 语法相同,但效果会有所不同。 在这⾥,我们可以假设类型T是hidden 。 后来,代码定义了两个T类型的对象: t1和t2 。 您可能会发现定义的t1没有任何可见性属性说明符。 因此, t1的可见性设置是从其类型直观地获得的。 并且,对于t2 ,代码将其定义为internal可见性。 这样的定义将在其类型T的可见性属性之前。 因此, t2的最终可见性是internal 。 这是基本规则。 但是,与对象相关联的实体(如成员函数和静态成员)的可见性由于不同的对象可见性定义⽽不会更改。 这意味着,在清单3中, T::gVal和T::Dosth()将具有T类定义的hidden可见性。 此外,这还意味着相同类型的对象( t1和t2 )仍然共享相同的功能表,⽆论为它们指定了什么可见性属性。 并且,如果程序员打算改变此类成员的可见性,例如以成员函数为例,则程序员需要直接为成员函数指定可见性属性。 清单4显⽰了如何将T::Dosth()的可见性属性更改为protected 。
清单4.更改类成员的可见性属性
class __attribute__((visibility("hidden"))) T // class T has "hidden" visibility
{
public:
static int gVal; // T::gVal is "hidden"
void __attribute__((visibility("protected"))) Dosth(); // T::Dosth() is now "protected"
};
T t1; // t1 is "hidden".
T __attribute__((visibility("internal"))) t2; // t2 has "internal" visibility.
可见性属性也可以应⽤于联合,枚举,名称空间和模板。 名称空间上的可见性将类似于作⽤域或上下⽂中的类。 为命名空间指定了可见性属性时,该属性仅应⽤于作⽤域中包含的任何符号。 但是,与此同时,功能仅限于封闭的范围。 清单5显⽰了⼀个⽰例。
清单5.命名空间的Visibility属性
namespace std __attribute__((visibility("hidden"))) {
void foo1() {}; // foo1() has "hidden" visibility.
}
namespace std {
void foo2() {} // The visibility of foo2() is "default"/"unspecified".
}
在此⽰例中, foo1()被hidden 。 但是, foo2()不会是hidden符号。 这意味着,如果您在⽂件中定义了名称空间std的可见性,那么编写具有相同名称空间std另⼀个⽂件的程序员将不会受到影响。 结果,这样的设计可以潜在地避免跨代码块(具有相同名称空间)的可见性设置的污染。
模板定义也同样有趣。 程序员可能总是对规则感到好奇,并且想知道当模板和type参数都具有可见性属性设置时如何设置可见性属性。 简⽽⾔之,如果主模板定义具有可见性,则该可见性将隐式传播到模板实例化。 否则,默认情况下,模板实例化的可见性与其模板参数相同。 有关⽰例,请参见清单6。
清单6.命名空间的Visibility属性
class __attribute__((visibility("internal"))) A {};
template <typename T> void foo1() {};
template <typename T> void __attribute__((visibility("hidden"))) foo2() {};
foo1<A>(); // The visibility of "foo1<A>()" is "internal" which is propagated from its argument "A".
foo2<int>(); // The visibility of "foo2<int>()" is "hidden" which is propagated from template.
foo2<A>(); // The visibility of "foo2<A>()" is "hidden".
此代码将类型A定义为internal ,⼀个通⽤模板函数foo1和具有hidden可见性的模板函数foo2 。 对于第⼀个实例foo1<A>,由于A具有可见性属性,因此该函数将从该模板参数传播可见性属性。 对于foo2<int> ,它从模板获取可见性属性。 ⽽且, foo2<A> 。 实例化忽略类类
型A的可见性属性。 因此,模板的可见性属性具有优先权。
语⽤
除了可见性属性外,还有另⼀种⽅法可以从源代码级别设置符号的可见性属性。 XL C / C ++编译器通
过引⼊可见性杂注来提供此类功能。 实⽤程序⽐可见性属性具有⼀些优势。 它允许程序员为多个声明设置可见性,⽽⽆需逐个符号定义可见性。 可见性编译指⽰语法如清单7所⽰。
清单7.可见性⽤法的语法
#pragma GCC visibility push(hidden)
#pragma GCC visibility pop
可见性编译指⽰仅包括两个堆栈操作。 堆栈顶部将显⽰当前的可见性属性。 当前可见性可以应⽤于在代码点之后定义的所有变量。 例如,在Linux上,当前上下⽂的默认可见性是default 。 如果程序员使⽤pragma 推送 hidden可见性,则在push pragma之后定义的所有声明的可见性都是hidden 。 除⾮遇到其他可见性实⽤程序,否则它将不会更改。 清单8显⽰了⼀个⽰例。
清单8.命名空间的Visibility属性
#pragma GCC visibility push(hidden)
int m; // "hidden" visibility
#pragma GCC visibility push(protected)
void foo() // "protected" visibility
#pragma GCC visibility pop
class A{}; // "hidden" visibility ("protected" visibility is popped)
#pragma GCC visibility pop
int n; // "default" visibility
#pragma GCC visibility pop // An error is emitted as there is no visibility pragma to pop.
请注意,如果可见性堆栈已为空,则弹出编译指⽰可能会在编译过程中引起错误,具体情况如下所⽰。
编译器选项
有时,程序员可能还想从编译器选项中设置可见性属性。 使⽤这些选项,它们可以更改嵌⼊在编译⽂件中的符号的可见性。 对于XL C / C ++编译器,您可以指定以下选项。
xlc -qvisibility=default | protected | hidden | internal | unspecified
注意, unspecified选项仅适⽤于AIX平台。 在Linux上, -qvisibility等效于–qvisibility=default 。 在AIX上, -qvisibility等效于–qvisibility=unspecified 。
可见性确定规则
由于可以通过可见性属性说明符,可见性编译指⽰或可见性选项设置声明可见性,因此了解有关编译器如何解决它们之间的冲突的规则⾮常重要。
实际上,编译器最终将通过检查以下顺序来解决声明的可见性:
可见性属性说明符(如果有)
如果是模板实例,则其主要模板定义的可见性
包含它的上下⽂(结构,类,联合,枚举,名称空间或编译指⽰)的可见性
否则,其类型的可见性
规则很简单。 ⾸先,可见性属性说明符如果是声明的⼀部分,则始终优先。 如果不正确,并且代码是模板实例化,则考虑主模板定义的可见性。 清单6向我们展⽰了这样的⼀个实例。
否则,声明的可见性由其封闭上下⽂确定。 声明的封闭上下⽂可以是,例如,全局变量的名称空间范围或其成员的类范围,依此类推。 有关⽰例,请参见清单9。
清单9.封闭上下⽂的Visibility属性
#pragma GCC visibility push(hidden)
class A // "hidden" from pragma
{
static int m; // "hidden" from class definition
void __attribute__((visibility("protected"))) foo() // foo is "protected"
};
在清单9中, class A被hidden因为该杂注定义了具有可见性hidden的封闭上下⽂。 它的成员也将hidden作为其可见性,因为其封闭上下⽂是A的定义。但是,由于存在属性说明符,因此foo设置为protected可见性。
作为最后⼀条规则,声明的可见性由其类型决定。 清单4已经显⽰了有关类类型的⽰例。
⽽对于没有任何可见性说明符的函数声明,其可见性由以下四个元素确定。
功能参数
函数返回类型
函数模板参数(类型或⾮类型)
从编译器传递的可见性选项
通过⽐较这四个元素,不难发现可见性最⼩的元素。 在这⾥,我们认为default是最可见的, protected是次要的, hidden位置是第三,
⽽internal是最不可见的。 该功能将从提供的四个元素中获得的可见性最⼩。 清单10显⽰了⼀个⽰例。
清单10.函数的可见性确定
class __attribute__((visibility("internal"))) A {};
A *foo (int a) {}; // Even with option -qvisibility=hidden, foo is "internal".
在此⽰例中,⽤户可能会观察到foo函数正在将指针类型返回给class A , class A是使⽤internal可见性定义的。 在全局上下⽂中,有⼀个选项定义可见性为hidden 。 foo的最终解析结果是internal因为它的可见性最⼩。
如果上⾯列出的规则不能应⽤于变量声明或函数,则会为它们设置默认的可见性。 默认可见性在以下部分中描述。
AIX上的可见性属性和向后兼容性
从上⼀篇⽂章中我们知道,在AIX上,默认情况下不可见符号,除⾮我们在链接阶段⼿动或借助CreateExportList导出它们。 但是,在Linux 上,符号默认情况下具有导出(即default )可见性。 这在AIX可见性属性和GNU可见性属性之间造成了差距。 为了向后兼容,在AIX
上,XL C / C ++不会像Linux⼀样设置所有要导出的符号。 它可能认为没有任何可见性设置的符号是⾮特定的可见性,与旧的AIX实现保持⼀致。 对于此类符号,AIX编译器,链接器和相关⼯具将像以前⼀样处理它。 但是,不明确的可见性并不意味着该符号是内部的或根本不可见的。 这只是为了保持兼容性⽽专门设计的可见性。
因此,正如我们在本⽂开头提到的那样,如果程序员未明确指定符号的可见性属性,则在Linux上,符号的可见性可能是默认值 。 但是在AIX上,可见性是不确定的 。
链接时的可见性冲突
在AIX上链接时的可见性冲突
从上⼀篇⽂章中,我们知道,在AIX系统上,可以通过使⽤导出⽂件来确定是否导出了库/可执⾏⽂件中的符号。 但是,由于IBM XL C / C ++ V13编译器引⼊了符号可见性属性,因此必须知道该功能如何与导出⽂件交互。
考虑a.cpp⽂件中的⼀个典型定义,如清单11所⽰。
清单11.⽂件a.cpp
int sym __attribute__((visibility("default")));
变量sym是使⽤源代码中的default (导出)属性定义的。 我们可以编译a.cpp⽂件以⽣成⼀个动态共享库liba.so。 我们希望sym是库中的导出符号。 但是,如果在链接步骤中给出了exportlist(导出⽂件)(如清单12所⽰),我们将-bE:exportlist选项传递给链接器以⽣成liba.so库(如清单13所⽰)。 ,通常会提出⼀个问题: sym最终是共享库中的全局可见(导出)符号。
清单12. exportlist⽂件
sym hidden
清单13.编译命令
xlC -G -o liba.so a.c -qpic -bE:exportlist
这样的问题给链接器/编译器设计带来了挑战。 但是作为设计师,我们注意到:
可见性属性将由AIX的XL C / C ++ V13引⼊。 它的⽬标是更多新代码和移植代码(即接受GNU可见性属性的代码)。
在链接阶段,exportlist⽬标中的可见性,通常在编译阶段之后调⽤。
因此,链接器最好使导出⽂件中的可见性关键字优先。 实际上,链接器可能必须处理来⾃导出⽂件(定义符号的可见性关键字)和诸如对象⽂件(提供符号的可见性属性)之类的模块的不同可见性关键字/属性组合。 这种处理也称为可视性冲突的解决 。 表1描述了链接器的⼯作⽅式。
表1.通过AIX链接程序解决的可见性冲突
导出⽂件中的关键字⽬标⽂件中的符号可见性属性
进⼝的已出⼝受保护的隐内部没有
出⼝经验值经验值经验值经验值错误经验值
受保护的保护保护保护保护错误保护
为 hidden错误没有没有没有错误没有
我 nternal错误没有没有没有没有没有
N 个(⽆关键字)没有经验值保护没有没有没有
EXP :符号已导出
PROT :导出具有受保护可见性的符号
否 :不导出符号
错误 :未导出符号。 打印错误消息,链接失败
在表1中,⾏标题显⽰了源模块中定义的符号可见性。 列标题是在导出⽂件中定义的符号关键字。 注意,在某些版本的AIX中,关键字可以导出⽽不是export 。 表1显⽰了链接器可见的符号的最终分辨率结果,包括:
符号在⽬标模块中是否可见(此类模块可以是最终构建的库)
导出的符号是否可以被抢占
如表1所⽰,如果导出⽂件声称该符号已导出或受保护 ,则该符号最终将被链接器解析为全局可见(不能抢占后者)。 同样,如果导出⽂件将符号描述为hidden或internal ,则该符号最终将不可见。 此外,如果未指定导出⽂件-bexpall/-bxpfull指定链接选项-bexpall/-bxpfull ,则对象⽂件中的符号可见性优先。 例如,对于某些⾮理性组合,该符号在⽬标⽂件中具有内部属性,⽽导出⽂件希望将其导出 ,则链接器将引发错误。 原因是导出⽂件可能会改变程序员的意图。
结果,对于前⾯提到的a.cpp⽂件, sym变量的符号最终将在全局范围内不可见。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论