GCC中的弱符号与强符号


我们经常在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错:
1 b.o:(.data+0x0): multiple definition of `global'
2 a.o:(.data+0x0): first defined here

 


        这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)
对于C/C++语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。我们也可以通过GCC的"__attribute__((weak))"来定义任何一个强符号为弱符号。注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用。比如我们有下面这段程序:
extern int ext;
int weak;
int strong = 1;
__attribute__((weak)) weak2 
= 2;
int main()
{
        
return 0;
}

 

上面这段程序中,"weak"和"weak2"是弱符号,"strong"和"main"是强符号,而"ext"既非强符号也非弱符号,因为它是一个外部变量的引用。
针对强弱符号的概念,链接器就会按如下规则处理与选择被多次定义的全局符号:
  • 规则1:不允许强符号被多次定义(即不同的目标文件中不能有同名的强符号);如果有多个强符号定义,则链接器报符号重复定义错误。
  • 规则2:如果一个符号在某个目标文件中是强符号,在其他文件中都是弱符号,那么选择强符号。
  • 规则3:如果一个符号在所有目标文件中都是弱符号,那么选择其中占用空间最大的一个。比如目标文件A定义全局变量global为int型,占4个字节;目标文件B定义global为double型,占8个字节,那么目标文件A和B链接后,符号global占8个字节(尽量不要使用多个不同类型的弱符号,否则容易导致很难发现的程序错误)。

弱引用和强引用  

目前我们所看到的对外部目标文件的符号引用在目标文件被最终链接成可执行文件时,它们须要被正确决议,如果没有找到该符号的定义,链接器就会报符号未定义错误,这种被称为强引用(Strong Reference)。与之相对应还有一种弱引用(Weak Reference),在处理弱引用时,如果该符号有定义,则链接器将该符号的引用决议;如果该符号未被定义,则链接器对于该引用不报错。链接器处理强引用和弱引用的过程几乎一样,只是对于未定义的弱引用,链接器不认为它是一个错误。一般对于未定义的弱引用,链接器默认其为0,或者是一个特殊的值,以便于程序代码能够识别。
在GCC中,我们可以通过使用"__attribute__((weakref))"这个扩展关键字来声明对一个外部函数的引用为弱引用,比如下面这段代码:
1 __attribute__ ((weakref)) void foo();
2 int main()
3 {
4         foo();
5 }
6 

 

我们可以将它编译成一个可执行文件,GCC并不会报链接错误。但是当我们运行这个可执行文件时,会发生运行错误。因为当main函数试图调用foo函数时,foo函数的地址为0,于是发生了非法地址访问的错误。一个改进的例子是:
1 __attribute__ ((weakref)) void foo();
2 int main()
3 {
4         if (foo)
5                foo();
6 }
7 

 

      这种弱符号和弱引用对于库来说十分有用,比如库中定义的弱符号可以被用户定义的强符号所覆盖,从而使得程序可以使用自定义版本的库函数;或者程序可以对某些扩展功能模块的引用定义为弱引用,当我们将扩展模块与程序链接在一起时,功能模块就可以正常使用;如果我们去掉了某些功能模块,那么程序也可以正常链接,只是缺少了相应的功能,这使得程序的功能更加容易裁剪和组合。
      在Linux程序的设计中,如果一个程序被设计成可以支持单线程或多线程的模式,就可以通过弱引用的方法来判断当前的程序是链接到了单线程的Glibc库还是多线程的Glibc库(是否在编译时有-lpthread选项),从而执行单线程版本的程序或多线程版本的程序。我们可以在程序中定义一个pthread_create函数的弱引用,然后程序在运行时动态判断是否链接到pthread库从而决定执行多线程版本还是单线程版本:
 1 #include <stdio.h>
 2 #include <pthread.h>
 3 int pthread_create( pthread_t*const pthread_attr_t*
 4 void* (*)(void*), void*) __attribute__ ((weak));
 5 int main()
 6 {
 7     if(pthread_create) 
 8     {
 9             printf("This is multi-thread version!\n");
10             // run the multi-thread version
11             // main_multi_thread()
12     } 
13     else 
14     {
15             printf("This is single-thread version!\n");   
16             // run the single-thread version
17             // main_single_thread()
18     }
19 }
20 

 

编译运行结果如下:
1 $ gcc pthread.c -o pt
2 $ ./pt
3 This is single-thread version!
4 $ gcc pthread.c -lpthread -o pt
5 $ ./pt
6 This is multi-thread version!

 


在GCC的官方文档中,对weak和weakref的描述如下:
http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function-Attributes

weak
The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker. 

weakref
weakref ("target")
The weakref attribute marks a declaration as a weak reference. 
Without arguments, it should be accompanied by an alias attribute naming the target symbol. Optionally, the target may be given as an argument to weakref itself. In either case, weakref implicitly marks the declaration as weak. Without a target, given as an argument to weakref or to alias, weakref is equivalent to weak.
1     static int x() __attribute__ ((weakref ("y")));
2     /* is equivalent to... */
3     static int x() __attribute__ ((weak, weakref, alias ("y")));
4     /* and to... */
5     static int x() __attribute__ ((weakref));
6     static int x() __attribute__ ((alias ("y")));

 

     
A weak reference is an alias that does not by itself require a definition to be given for the target symbol. If the target symbol is only referenced through weak references, then the becomes a weak undefined symbol. If it is directly referenced, however, then such strong references prevail, and a definition will be required for the symbol, not necessarily in the same translation unit.
The effect is equivalent to moving all references to the alias to a separate translation unit, renaming the alias to the aliased symbol, declaring it as weak, compiling the two separate translation units and performing a reloadable link on them.
At present, a declaration to which weakref is attached can only be static.

posted @ 2010-01-27 22:03  kernel@hcy  阅读(10906)  评论(0编辑  收藏  举报