common块探究

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段的数据都是零,所以干脆在装载的时候才在虚拟内存空间里分配