首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

控制共享库的符号可见性-符号可见性简介(2)

控制共享库的符号可见性-符号可见性简介(2)

控制符号可见性的方式在后面的讨论中,我们将用到下面的 C++ 代码片段:
清单 1. a.C
1
2
3
4
5
6
7
8
9
int myintvar = 5;

int func0 () {
  return ++myintvar;
}

int func1 (int i) {
  return func0() * i;
}




在 a.C 中,我们定义了一个变量 myintvar,以及两个函数 func0 和 func1。默认情况下,在 AIX 平台上创建共享库时,编译器和链接器以及 CreateExportList 工具会让所有三个符号都可见。我们可以利用 dump 二进制工具从 Loader Symbol Table Information 检查这一情况:
1
2
3
4
5
6
7
8
9
$ xlC -qpic a.C -qmkshrobj -o libtest.a
$ dump -Tv libtest.a

                        ***Loader Symbol Table Information***
[Index]      Value      Scn     IMEX Sclass   Type           IMPid Name

[0]     0x20000280    .data      EXP     RW SECdef        [noIMid] myintvar
[1]     0x20000284    .data      EXP     DS SECdef        [noIMid] func0__Fv
[2]     0x20000290    .data      EXP     DS SECdef        [noIMid] func1__Fi




这里,“EXP”表示符号是导出的。函数名称 func0 和 func1 被 C++ 重整规则(mangling rule)进行了重整(但是,不难猜出名称的意思)。dump 工具的 -T 选项显示 Loader Symbol Table Information,动态链接器将用到此信息。在本例中,a.C 中的所有符号都被导出。但是从库编写者的角度,本例中我们可能只想导出 func1。全局符号 myintvar 和函数 func0 被认为只保持/改变内部状态,或者说只在局部使用。因此,对于库编写者来说,让它们不可见至关重要。
我们至少有三种方式可以达此目的。包括:使用 static 关键字,定义 GNU visibility 属性,以及使用导出列表。每种方式都有各自不同的功用和缺点。下面就来看看这些方式。
1. 使用 static 关键字C/C++ 中的 static 可能是一个最常用的关键字,因为它可以为变量指定作用域和存储。对于作用域,可以说成它为文件中的符号禁用了外部链接。这意味着,带有关键字 static 的符号永远不会是可链接的,因为编译器不为链接器留下关于此符号的任何信息。这是一种语言级别的控制,是最简单的一种隐藏符号的方式。
我们来给上面的例子添加 static 关键字吧:
清单 2. b.C
1
2
3
4
5
6
7
8
9
static int myintvar = 5;

static int func0 () {
  return ++myintvar;
}   

int func1 (int i) {
  return func0() * i;
}




生成共享库并再次查看 Loader Symbol Table Information,可以看到预期的效果:
1
2
3
4
5
6
7
$ xlC -qpic a.C -qmkshrobj -o libtest.a
$ dump -Tv libtest.a

                        ***Loader Symbol Table Information***
[Index]      Value      Scn     IMEX Sclass   Type           IMPid Name

[0]     0x20000284    .data      EXP     DS SECdef        [noIMid] func1__Fi




现在,如信息所示,只有 func1 被导出。然而,尽管 static 关键字可以隐藏符号,但是它还定义了一条额外的规则,即变量或函数只可以在定义它们的文件范围内使用。因此,如果我们定义:
1
extern int myintvar;




后面,在文件 b.C 中,您可能想要从 a.o 和 b.o 构建 libtest.a。您这样做时,链接器将显示一条错误消息,指出定义在 b.C 中的 myintvar 无法被链接,因为链接器没有在其他地方找到定义。这中断了相同模块内的数据/代码共享,而这种共享通常是程序员所需要的。因此,这种方法更多地用作文件内变量/函数的可见性控制,而不用于低级别符号的可见性控制。实际上,大多数变量/函数不会依赖于 static 关键字来控制符号可见性。因此,我们可以考虑第二种方法:
2. 定义 visibility 属性(仅针对 GNU)下一个控制符号可见性的备选方法是使用 visibility 属性。ELF 应用程序二进制接口 (ABI) 定义符号的可见性。一般来说,它定义 4 个类,但是大多数情况下,最常用的只有其中两个:
  • STV_DEFAULT - 用它定义的符号将被导出。换句话说,它声明符号是到处可见的。
  • STV_HIDDEN - 用它定义的符号将不被导出,并且不能从其他对象进行使用。
注意,这只是 GNU C/C++ 的一个扩展。因此,目前 PowerLinux 客户可以将它用作符号的 GNU 属性。下面是针对本文示例情况的一个例子:
1
2
3
4
5
int myintvar __attribute__ ((visibility ("hidden")));
int __attribute__ ((visibility ("hidden"))) func0 () {
  return ++myintvar;
}
...




要定义 GNU 属性,需要包含 __attribute__ 和用括号括住的内容。您可以将符号的可见性指定为 visibility(“hidden”)。在上面的示例中,我们可以将 myintvar 和 func0 标记为 hidden 可见性。这将不允许它们在库中被导出,但是可以在源文件之间共享。实际上,隐藏的符号将不会出现在动态符号表中,但是还被留在符号表中用于静态链接。这是一种良好定义的行为,完全可以达到我们的目的。它显然优于 static 关键字方法。
注意,对于用 visibility 属性指定的变量,将它声明为 static 可能会让编译器感到混淆。因此,编译器会显示一条警告消息。
ELF ABI 也定义其他可见性模式:
  • STV_PROTECTED:符号在当前可执行文件或共享对象之外可见,但是不会被覆盖。换句话说,如果共享库中的一个受保护符号被该共享库中的另一个代码引用,那么此代码将总是引用共享库中的此符号,即便可执行文件定义了相同名称的符号。
  • STV_INTERNAL:符号在当前可执行文件或共享库之外不可访问。
注意,此方法目前不受 XL C/C++ 编译器支持,即便在 PowerLinux 平台上亦是如此。但是,我们还有别的方法。












  • 在代码编写阶段编写更多的代码来设置符号的可见性
返回列表