COMMON块
大部分教材,或者Blog都会讲到c系语言中未初始化的全局变量或静态变量
的内存空间是在.bss段
今天就来测试一下,gcc/g++的版本如下
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~$ gcc --version
gcc (Ubuntu 5.4.1-2ubuntu1~14.04) 5.4.1 20160904
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~$ g++ --version
g++ (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
代码如下
//main.c
#include <stdio.h>
int m;
intmain() {
printf("hello world; m = %d\n", m);
return 0;
}
//生成中间文件
gcc -c main.c -o main_gcc.o
g++ -c main.c -o main_g++.o
常看符号表
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_gcc.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.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 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000004 4 OBJECT GLOBAL DEFAULT COM m
10: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_g++.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.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 0 SECTION LOCAL DEFAULT 7
7: 0000000000000000 0 SECTION LOCAL DEFAULT 8
8: 0000000000000000 0 SECTION LOCAL DEFAULT 6
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 4 m
10: 0000000000000000 34 FUNC GLOBAL DEFAULT 1 main
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND printf
可以看见当作为c++的语言来编译,就直接是Ndx=4了,也就是bss段
但是作为纯正的gcc编译c语言却不是,是一个COM的符号,这东西主要解决了什么问题?
一个弱符号定义在多个目标文件中,而它们的类型又不同(即大小不同)
显然,由于c++把m
这个变量内存存在了 .bss section,也就是不允许其他文件再去定义这个变量m
实验
接下来再添加个c系代码
//m.c
long long m = 12;
1. g++
先验证c++的方式
//m.c生成中间文件
g++ -c m.c -o m_g++.o
//生成可执行文件
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ g++ main_g++.o m_g++.o -o main_m_g++
m_g++.o:(.data+0x0): multiple definition of `m'
main_g++.o:(.bss+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `m' changed from 4 in main_g++.o to 8 in m_g++.o
collect2: error: ld returned 1 exit status
报错,重复定义变量m
2. gcc的c编译器
先验证gcc的方式
//m.c生成中间文件
gcc -c m.c -o m_gcc.o
//生成可执行文件
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc m_gcc.o main_gcc.o -o main_m_gcc
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_m_gcc
hello world; m = 12
直接看看最终的变量m
在可执行文件中 占用字节多大
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_m_gcc | grep ' m'
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS m.c
37: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
60: 0000000000601040 8 OBJECT GLOBAL DEFAULT 24 m
62: 0000000000400536 34 FUNC GLOBAL DEFAULT 13 main
8字节,也就是说 m.c中的 double定义 覆盖了 main.c中int定义
看一下索引24是哪个段
readelf -S main_m_gcc
...
[24] .data PROGBITS 0000000000601030 00001030
0000000000000018 0000000000000000 WA 0 0 8
...
符合m.c的初始化的全局变量内存在.data段
对比一下直接编译main.c生成 可执行文件 main_gcc
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc main_gcc.o -o main_gcc
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_gcc
hello world; m = 0
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -s main_gcc | grep ' m'
36: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.c
59: 0000000000601044 4 OBJECT GLOBAL DEFAULT 25 m
61: 0000000000400536 34 FUNC GLOBAL DEFAULT 13 main
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ readelf -S main_gcc
...
[25] .bss NOBITS 0000000000601040 00001040
0000000000000008 0000000000000000 WA 0 0 4
...
符合未初始化的全局变量内存存放在.bss段
,只不过纯gcc的c语言编译器生成中间文件的时候,还是标记为COMMON符号,链接生成可执行文件才会把放在.bss段
危险
在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。
举个危险的例子
//main.c
int m = 12;
int n = 11;
int main() {
printf("m = %d\tn = %d\n", m, n);
setzerom();
printf("m = %d\tn = %d\n", m, n);
return 0;
}
//m.c
long long m;
void setzerom()
{
m = 0;
}
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ gcc -w main.c m.c -o main_m_gcc
/usr/bin/ld: Warning: alignment 4 of symbol `m' in /tmp/ccgkd2y4.o is smaller than 8 in /tmp/cc7iEkVJ.o
chainhelen@iZwz92pw32r9w6beu4jlfpZ:~/tmp/common$ ./main_m_gcc
m = 12 n = 11
m = 0 n = 0
正常来讲,没有动变量n的值,不应该出现它的值也变成 0 的现象
在m.c中,变量m其实访问到了main.c中的n,因为它是8字节的long long 弱符号
在第一处print地方,加上断点,gdb调试可知原因
(gdb) p &m
$3 = (int *) 0x601040 <m>
(gdb) p &n
$4 = (int *) 0x601044 <n>
(gdb) x/8b 0x601040
0x601040 <m>: 0x0c 0x00 0x00 0x00 0x0b 0x00 0x00 0x00
(gdb) nm = 12 n = 11
(gdb) n
(gdb) x/8xb 0x601040
0x601040 <m>: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
但是如下代码却不会有事
//main.c
int m;
int n = 11;
int main() {
printf("m = %d\tn = %d\n", m, n);
setzerom();
printf("m = %d\tn = %d\n", m, n);
return 0;
}
//m.c
long long m = 12;
void setzerom()
{
m = 0;
}
总结
在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。
对于它们,下列三条规则使用:
① 同名的强符号只能有一个,否则编译器报"重复定义"错误
② 允许一个强符号和多个弱符号,但定义会选择强符号的
③ 当有多个弱符号相同时,链接器选择占用内存空间最大的那个
对了,上面如果加上编译项 -fno-common 就会报错,就变成了c++的那种了
实际.bss段在可执行文件里面,是没有分配内存的,因为.bss段的数据都是零,所以干脆在装载
的时候才在虚拟内存空间里分配