弱符号与强符号

弱符号与强符号

在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。

对于它们,下列三条规则使用:

① 同名的强符号只能有一个,否则编译器报"重复定义"错误。

② 允许一个强符号和多个弱符号,但定义会选择强符号的。

③ 当有多个弱符号相同时,链接器选择占用内存空间最大的那个。

对于C++来说,弱符号通常来源于未初始化的全局变量。而默认情况下,编译器将函数和初始化了的全局变量作为强符号。

可以通过gcc的 __attribute__((weak)) 来定义任何一个强符号为弱符号。

不同的目标文件中不能有同名的强符号,否则不能链接在一起。

如果一个符号在某个目标文件中是强符号,在其它文件中都是弱符号,那么该名称在链接时选择强符号。

如果一个符号在所有的目标文件中都是弱符号,则选择占用空间(字节数)最大的一个。

相应的有 弱引用与强引用的概念。

可以将一个外部函数申明为弱引用,比如下面的做法:

__attribute__((weakref)) void foo();

int main()

{

    if(foo)  foo();

}

多个符号定义类型不一致及其处理

不一致有三种情况:

  • 两个或两个以上强符号类型不一致;
  • 有一个强符号,其他都是弱符号,出现类型不一致;
  • 两个或者两个以上弱符号类型不一致。

第一种情况,在编译的时候会提示多重定义错误,因为多个同名强符号定义本身就是非法的。

后面两种情况需要链接器(ld)来处理。

编译器把未初始化的全局变量作为弱符号处理。比如在某个.o中定义了一个未初始化的全局变量 global_uninit_var。此时用 readelf -s查看该变量会看到:

st_name = "global_uninit_var"

st_value = 4

st_size = 4

st_info = 0x11 STB_GLOBAL STT_OBJECT

st_other = 0

st_shndx = 0xfff2 SHN_COMMON

发现这个变量是一个 SHN_COMMON类型。这里使用的是一种成为 Common Block的机制,是一种事先声明临时使用空间的机制。

如果在另一个.o文件也定义了相同名字的 global_uninit_var 变量,且未初始化,类型为占8个字节的double,则按照common block的链接规则,在最终链接后的输出文件中,global_uninit_var的大小会以输入文件中占用空间最大的那个为准。在上面这个例子中,global_uninit_var最终所占的空间是8个字节。

COMMON类型的链接规则是针对符号都是弱符号的情况,如果其中有个符号是强符号,其他都是弱符号,则最终输出结果中的符号所占空间与强符号相同。如果链接过程中有弱符号大于强符号,那么ld链接器会报如下警告:

ld: warning: alignment 4 of symbol `global' in a.o is smaller than 8 in b.o

正是由于未初始化的全局变量(弱符号)其大小在编译某个目标文件时未可知,所以那时无法为其在 .bss 节区分配空间。在链接过程中,任何一个弱符号的最终大小都可以确定了,所以它可以在最终输出文件的bss段为其分配空间。所以,从最终的输出可执行文件来看,未初始化的全局变量是放在 .bss 节区的。

GCC 可以使用 -fno-common 使得我们可以不以COMMON 块机制处理未初始化的全局变量。这时,该符号就相当于一个强符号。

int global __attribute__((no_common));

当然,如果程序员足够小心,在声明全局变量时记住在该加 “extern” 关键字时加上它,很多的弱符号类型不一致问题可避免。

补充:

最近在看《程序员的自我修养》,知道现在的编译器和链接器支持一种叫COMMOM块(Common Block)的机制,这种机制用来解决 一个弱符号定义在多个目标文件中,而它们的类型又不同(即大小不同) 的情况。

目标文件中,编译器将未初始化的全局变量放在了COMMON段,未初始化的静态变量(包括全局和局部静态变量)放在BSS段。

---------------------------------------------------------------------------------------------------------------------------------------

对于全局变量来说,如果初始化了不为0的值,那么该全局变量存储在.data段;

如果初始化的值为0, 那么将其存储在.BSS;(依然是强符号)

如果没有初始化,那么编译时将其存储在COMMON块,等到链接时再将其放入到.BSS段。(这点不同的编译器会有所不同,有的编译器会直接把没有初始化的全局变量放在.BSS段,而没有COMMON块机制)

整理自:http://www.cnblogs.com/whos/archive/2010/10/20/1856274.html

https://www.cnblogs.com/x_wukong/p/5913449.html

猜你喜欢

转载自blog.csdn.net/qq_34272143/article/details/81665976