好记性不如铅笔头

C && C++, 编程

【转】C语言中的强符号与弱符号

参考文章【 http://116.62.110.235/blog/c-weak-strong-alias/

本文转自【 https://blog.csdn.net/astrotycoon/article/details/8008629 】,有删改。

一、概述

在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。
对于它们,下列三条规则使用:
① 同名的强符号只能有一个,否则编译器报”重复定义”错误。
② 允许一个强符号和多个弱符号,但定义会选择强符号的。
③ 当有多个弱符号相同时,链接器选择最先出现那个,也就是与链接顺序有关。
二、哪些符号是弱符号?
我们经常在编程中碰到一种情况叫符号重复定义。多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误。比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错。
这种符号的定义可以被称为强符号(Strong Symbol)。有些符号的定义可以被称为弱符号(Weak Symbol)。对于C语言来说,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号(C++并没有将未初始化的全局符号视为弱符号)。我们也可以通过GCC的”__attribute__((weak))“来定义任何一个强符号为弱符号。注意,强符号和弱符号都是针对定义来说的,不是针对符号的引用。比如我们有下面这段程序:

extern int ext;
int weak1;
int strong = 1;
int __attribute__((weak)) weak2 = 2; 
int main()
{
        return 0;
}

上面这段程序中,”weak1″和”weak2″是弱符号,”strong”和”main”是强符号,而”ext”既非强符号也非弱符号,因为它是一个外部变量的引用。下面一段话摘自wikipedia:

In computing, a weak symbol is a symbol definition in an object file or dynamic library that may be overridden by other symbol definitions. Its value will be zero if no definition is found by the loader.

换句话说,就是我们可以定义一个符号,而该符号在链接时可以不解析。

void __attribute__((weak)) f();
int main(void)
{
        if (f)
        f();
        return 0;
}

首先声明了一个符号f(),属性为weak,但并不定义它,这样,链接器会将此未定义的weak symbol赋值为0,也就是说f()并没有真正被调用,试试看,去掉if条件,肯定core dump!

那么当出现multiple symbols时,会如何呢?

$ cat mul.c
int main(void)
{
        f();
        return 0;
}
$ cat s1.c
#include <stdio.h>
void f(void)
{
        printf("1st strong f from %s\n", __FILE__);
}
$ cat s2.c
#include <stdio.h>
void f(void)
{
        printf("2nd strong f from %s\n", __FILE__);
}
$ cat w1.c
#include <stdio.h>
void __attribute__((weak)) f(void)
{
        printf("1st weak f from %s\n", __FILE__);
}
$ cat w2.c
#include <stdio.h>
void __attribute__((weak)) f(void)
{
        printf("2nd weak f from %s\n", __FILE__);
}
$ gcc -c mul.c s1.c s2.c w1.c w2.c

如果我们这样做:
$ ar qs libw.a w1.o w2.o
$ ar qs libs.a s1.o s2.o
再编译:
$ gcc -o test2 mul.o -L. -lw -ls
$ ./test2 
1st weak f from w1.c
再改成这样编译:
$ gcc -o test2 mul.o -L. -ls -lw
$ ./test2
1st strong f from s1.c

原因就是GCC(准确地说是链接器)对待库是不一样的 —— 默认的,链接器使用第一个找到的符号,后面的就不搜索了。
不过我们也可以强制链接器搜索所有的库,办法如下:

$ ar qs libw.a w1.o w2.o
$ ar qs libs.a s1.o s2.o
$ gcc -o test2 mul.o -L. -Wl,–whole-archive -lw -ls -Wl,–no-whole-archive
./libs.a(s2.o): In function `f’:
s2.c:(.text+0x0): multiple definition of `f’
./libs.a(s1.o):s1.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status
重新如下操作:
$ rm libw.a libs.a
$ ar qs libw.a w1.o w2.o
$ ar qs libs.a s1.o
$ gcc -o test2 mul.o -L. -Wl,–whole-archive -lw -ls -Wl,–no-whole-archive
$ ./test2
1st strong f from s1.c
现在可以了,尽管-lw在前!

参考链接:
http://blog.csdn.net/chgaowei/article/details/7173436 (新手小心:c语言的强符号和弱符号)
http://www.embedded-bits.co.uk/2008/gcc-weak-symbols/ (GCC Weak Symbols)
http://write.blog.csdn.net/postedit/8008629 ( 什么是weak symbol?)
http://winfred-lu.blogspot.com/2009/11/understand-weak-symbols-by-examples.html (Understand Weak Symbols by Examples)
http://discusstolearn.blogspot.sg/2012/11/symbol-resolution-weak-symbols-how.html (Symbol Resolution, Weak Symbols, How compiler resolves multiple Global Symbols)
http://wanderingcoder.net/2012/06/30/multiply-defined-symbols/  ( Dealing with multiply defined symbols)
http://www.cnblogs.com/whos/archive/2010/10/20/1856274.html(弱符号与强符号概念)
http://www.searchtb.com/2013/03/compile_problems_about_strong_weak_symbols.html (分享两个强符号,弱符号引起的编译问题)
http://blog.csdn.net/glp_hit/article/details/8788963 (强符号 弱符号)
——————————–

最近在看《程序员的自我修养》,知道现在的编译器和链接器支持一种叫COMMOM块(Common Block)的机制,这种机制用来解决 一个弱符号定义在多个目标文件中,而它们的类型又不同(即大小不同) 的情况。
在目标文件中,编译器将未初始化的全局变量放在了COMMON段,未初始化的静态变量(包括全局和局部静态变量)放在BSS段。

对于全局变量来说,如果初始化了不为0的值,那么该全局变量存储在.data段;
如果初始化的值为0, 那么将其存储在.BSS;(依然是强符号)
如果没有初始化,那么编译时将其存储在COMMON块,等到链接时再将其放入到.BSS段。(这点不同的编译器会有所不同,有的编译器会直接把没有初始化的全局变量放在.BSS段,而没有COMMON块机制)
 
为什么这样处理呢?
我们可以想到,当编译器将一个编译单元编译成目标文件的时候,如果该编译单元包含了弱符号(未初始化的全局变量就是典型的弱符号),那么该弱符号最终所占空间的大小此时是未知的,因为有可能其他编译单元中同符号名称的弱符号所占的空间比本编译单元该符号所占的空间要大。所以编译器此时无法为该弱符号在BSS段分配空间,因为所需要的空间大小此时是未知的。但是链接器在链接过程中可以确定弱符号的大小,因为当链接器读取所有输入目标文件后,任何一个弱符号的最终大小都可以确定了,所以它可以在最终的输出文件的BSS段为其分配空间。所以总体来看,未初始化的全局变量还是被放在BSS段。       ——摘自《程序员的自我修养》

简而言之,在目标文件中没有将未初始化的全局变量像未初始化的静态变量那样放在BSS段,而是放在COMMON块,是因为现在的编译器和链接器允许不同类型的弱符号存在,最本质的原因是链接器无法判断各个符号的类型是否一致。

有了COMMON块之后就可以很好的解决这个问题了。

补充:
编程中我们可以使用GCC的“-fno-common”把所有的未初始化的全局变量不以COMMON块的形式处理,也可以使用“最近在看《程序员的自我修养》,知道现在的编译器和链接器支持一种叫COMMOM块(Common Block)的机制,这种机制用来解决 一个弱符号定义在多个目标文件中,而它们的类型又不同(即大小不同) 的情况。
在目标文件中,编译器将未初始化的全局变量放在了COMMON段,未初始化的静态变量(包括全局和局部静态变量)放在BSS段。

对于全局变量来说,如果初始化了不为0的值,那么该全局变量存储在.data段;
如果初始化的值为0, 那么将其存储在.BSS;(依然是强符号)
如果没有初始化,那么编译时将其存储在COMMON块,等到链接时再将其放入到.BSS段。(这点不同的编译器会有所不同,有的编译器会直接把没有初始化的全局变量放在.BSS段,而没有COMMON块机制)
 
为什么这样处理呢?
我们可以想到,当编译器将一个编译单元编译成目标文件的时候,如果该编译单元包含了弱符号(未初始化的全局变量就是典型的弱符号),那么该弱符号最终所占空间的大小此时是未知的,因为有可能其他编译单元中同符号名称的弱符号所占的空间比本编译单元该符号所占的空间要大。所以编译器此时无法为该弱符号在BSS段分配空间,因为所需要的空间大小此时是未知的。但是链接器在链接过程中可以确定弱符号的大小,因为当链接器读取所有输入目标文件后,任何一个弱符号的最终大小都可以确定了,所以它可以在最终的输出文件的BSS段为其分配空间。所以总体来看,未初始化的全局变量还是被放在BSS段。       ——摘自《程序员的自我修养》

简而言之,在目标文件中没有将未初始化的全局变量像未初始化的静态变量那样放在BSS段,而是放在COMMON块,是因为现在的编译器和链接器允许不同类型的弱符号存在,最本质的原因是链接器无法判断各个符号的类型是否一致。

有了COMMON块之后就可以很好的解决这个问题了。

补充:
编程中我们可以使用GCC的“-fno-common”把所有的未初始化的全局变量不以COMMON块的形式处理,也可以使用“__attribute__ ((nocommon))”,如下:
int global __attribute__ ((nocommon));  /* strong symbol */
一旦一个未初始化的全局变量不是以COMMON块的形式存在,那么它就相当于一强符号,如果其他目标文件中还有同一个变量的强符号定义,链接时就会发生符号重复定义错误。

参考链接:
http://blog.chinaunix.net/uid-23629988-id-2888209.html(通过未初始化全局变量,研究BSS段和COMMON段的不同)
http://blog.copton.net/articles/linker/index.html ( C++ and the linker)
http://www.lurklurk.org/linkers/linkers.html ( Beginner’s Guide to Linkers)
https://thunked.org/programming/code-obfuscation-with-linker-symbol-abuse-t100.html

Leave a Reply

18 − 12 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据