為什么全局變量一定要初始化?


一、初始化規則部分

在說明為什么要初始化之前,先提及下 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 \n", 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 \n", 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 的這個特性,如果有同名的全局變量,鏈接時就會產生出錯誤,便於用戶知曉問題的所在。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM