一、初始化规则部分
在说明为什么要初始化之前,先提及下 C 语言的初始化规则,以备后用。
可能大家在对数组进行初始化时用的是这样的方法:
char buf[10] = {0};
那么 char buf[10] = {1};是不是将每个数组中的每个元素都初始化为 1 了呢?
其实不然,根据编译器的特性,在指定初始化元素时,如果元素的个数少于数组元素的总个数,那么其它的元素将会初始化为 0。
我们可以用一段代码来验证这个特性:
1 #include <stdio.h> 2 3 int main() 4 { 5 char buf[10] = {1}; 6 7 return 0; 8 }
其反汇编代码如下:
1 <main>: 2 push {fp} ; (str fp, [sp, #-4]!) 3 add fp, sp, #0 4 sub sp, sp, #20 5 sub r3, fp, #16 6 mov r2, #0 7 str r2, [r3] 8 str r2, [r3, #4] 9 strh r2, [r3, #8] 10 mov r3, #1 11 strb r3, [fp, #-16] 12 mov r3, #0 13 mov r0, r3 14 add sp, fp, #0 15 pop {fp} ; (ldr fp, [sp], #4) 16 bx lr
由其中的部分编译代码 6-11 行可知,程序只是将数组的第一个字节赋值为 1,而其余字节被赋值为 0。到此可以得出结论,并非是所有的元素都被初始化为 1。
二、全局变量初始化部分
下面说一下我们创建的全局变量时为什么要初始化,如果想创建一个初始值为 0 的变量,那么 “ = 0 ” 的赋值操作可不可以省略呢?
费话不多说,用代码来说明一切。
假如有以下场景,某公司为实现一个项目,A程序员编写了 module_a.c 程序,他的同事B编写了 module_b.c 程序,最终两个人编写的源码如下:
1 /* module_a.c */ 2 #include <stdio.h> 3 4 void function(void); 5 6 int global = 0; 7 8 int main() 9 { 10 global = 3; 11 function(); 12 printf("main: %d ", global); 13 return 0; 14 }
1 /* module_b.c */ 2 #include <stdio.h> 3 4 int global; 5 6 void function(void) 7 { 8 global = 6; 9 printf("function: %d ", global); 10 return 0; 11 }
二人心有灵犀,定义了同样的全局变量 global,但由于 B 一时的疏忽,忘了将 global 进行初始化。
编译竟可以正常通过,且运行结果如下:
function: 6
main: 6
我们可以看到,同事 A 编译的模块产生了莫名其妙的运行结果。为什么定义了同名的全局变量,编译器不会报错呢? 我们继续探索。
首先将源文件分别编译为目标文件,然后使用 readelf 工具查看内容。
arm-linux-gcc -c module_x.c
arm-linux-readelf -a module_x.o
作者只摘取了其中的有关内容,其中 a 模块中的 global 标识如下:
... [4] .bss NOBITS 00000000 000078 000004 00 WA 0 0 4 ... 13: 00000000 4 OBJECT GLOBAL DEFAULT 4 global ...
b 模块中的 global 标识如下:
... 12: 00000004 4 OBJECT GLOBAL DEFAULT COM global ...
由此可得知,初始化为 0 的全局变量是合并至 .bss 段,而未初始化的全局变量合并到了 COM(common block) 段。原因是 gcc 编译器的缺省行为和传统 unix c 编译器一致,将未初始化的全局变量放入到 common block 中, common block 相当于弱符号(weak symbol),所以链接的时候并不会报错,这或许可能是一个很难被找出的 BUG。
在网络上找到了以下总结:
同名的弱符号和 global 符号链接不会出错,链接器会选择 global 符号。同理,如果有一个 global 符号和多个在 common block 中的重名,那么链接器会选取global符号。换句话说,链接器认为未初始化的全局变量是weak symbol。
当然,避免这种情况发生的办法也是有的,我们可以在编译时加上 -fno-common 属性来关闭 gcc 的这个特性,如果有同名的全局变量,链接时就会产生出错误,便于用户知晓问题的所在。