强符号:编译器默认函数和初始化的全局变量为强符号
弱符号:未初始化的全局变量为弱符号。也可以通过atrribute((weak))来指定。
编译器关于强弱符号的规则有:
(1)强符号不允许多次定义,但强弱可以共存;
(2)强弱共存时,强覆盖弱;
(3)都是弱符号时,选择占用空间最大的,如选择double类型的而不选择int类型的。
假如有文件a.c
long int i = 5;
b.c如下:
#include<stdio.h>
int i;
long int global_longint=4;
int main(int argc, char** argv)
{
static int x;
static char arr[255];
printf("i = %d\n", i);
return 0;
}
gcc b.c -c -o b.o
readelf -s b.o
Symbol table '.symtab' contains 15 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS b.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
3: 0000000000000000 0 SECTION LOCAL DEFAULT 3
4: 0000000000000000 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 255 OBJECT LOCAL DEFAULT 4 arr.2290
7: 0000000000000100 4 OBJECT LOCAL DEFAULT 4 x.2289
8: 0000000000000000 0 SECTION LOCAL DEFAULT 7
9: 0000000000000000 0 SECTION LOCAL DEFAULT 8
10: 0000000000000000 0 SECTION LOCAL DEFAULT 6
11: 0000000000000001 1 OBJECT GLOBAL DEFAULT COM i
12: 0000000000000000 8 OBJECT GLOBAL DEFAULT 3 global_longint
13: 0000000000000000 49 FUNC GLOBAL DEFAULT 1 main
14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
Ndx表示相应的符号位于elf文件下的哪一段,比如上面global_longint就是位于elf的第三个段,static 局部变量位于第四个段。
readelf -S b.o
共有 13 个节头,从偏移量 0x360 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000031 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000298
0000000000000048 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000078
0000000000000008 0000000000000000 WA 0 0 8
[ 4] .bss NOBITS 0000000000000000 00000080
0000000000000104 0000000000000000 WA 0 0 32
[ 5] .rodata PROGBITS 0000000000000000 00000080
0000000000000008 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000088
0000000000000035 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000bd
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000c0
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 000002e0
0000000000000018 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 000002f8
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000000f8
0000000000000168 0000000000000018 12 11 8
[12] .strtab STRTAB 0000000000000000 00000260
0000000000000032 0000000000000000 0 0 1
我们可以看到第三个段为数据段,第四个段为bss段,也印证了初始化的全局变量位于数据段,未初始化的静态位于bss段。
这里我们可以发现,bss起始于00000080,结束于00000080,bss的大小为0,也就是说,bss段只有存在于section header table (段头表,这个表记录所有段的基本信息,例如段大小,在elf的具体位置)中的那一项,而没有相应的实体段。
这是因为,未初始化变量嘛,不用记录具体数值,我们只要记录所有未初始化变量的总大小就行了,而记录总大小,在section header table中的段大小这个字段就足够了。所以bss段实体就没有存在的必要了。
然后,我们注意看弱引用i是未初始化全局变量,是在common段中,而不是在我们所认为的bss段中。
这是因为,未初始化变量是弱引用,如果多个文件存在多个相同的弱引用,其大小取其最大那个类型,现在我们只是编译一个文件,根本不知道是否还存在其他相同的弱引用,所以,编译阶段无法确定其大小,但是,在链接时,我们已经可以看到所有文件了,也就可以看到所有弱引用了,这个时候,我们就可以确定其大小。所以,为初始化全局变量在编译阶段其实是存在于common段的,然后链接的时候,比较所有文件的common段,确定为初始化全局变量弱引用大小,将其放到bss段中。
所以我们现在把两个链接
gcc b.c a.c -o main -g
readelf -s main
62: 0000000000601038 8 OBJECT GLOBAL DEFAULT 25 global_longint
63: 0000000000601028 0 NOTYPE GLOBAL DEFAULT 25 __data_start
64: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
65: 0000000000601030 0 OBJECT GLOBAL HIDDEN 25 __dso_handle
66: 00000000004005e0 4 OBJECT GLOBAL DEFAULT 16 _IO_stdin_used
67: 0000000000400560 101 FUNC GLOBAL DEFAULT 14 __libc_csu_init
68: 0000000000601188 0 NOTYPE GLOBAL DEFAULT 26 _end
69: 0000000000400430 42 FUNC GLOBAL DEFAULT 14 _start
70: 0000000000601040 8 OBJECT GLOBAL DEFAULT 25 i
71: 0000000000601048 0 NOTYPE GLOBAL DEFAULT 26 __bss_start
72: 0000000000400526 49 FUNC GLOBAL DEFAULT 14 main
因为上面a.c中i强引用是有初始值的,所以最后i变为初始化全局变量,而25是.data段。我们将a.c中的i改为未初始化的,再链接,可以看到,i最后被放在bss段。