程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 通過未初始化全局變量,研究BSS段和COMMON段的不同

通過未初始化全局變量,研究BSS段和COMMON段的不同

編輯:關於C
本文的copyleft歸[email protected]所有,使用GPL發布,可以自由拷貝,轉載。但轉載請保持文檔的完整性,注明原作者及原鏈接,嚴禁用於任何商業用途。
作者:[email protected]
博客:linuxfocus.blog.chinaunix.net
 
 
 
最近正在重溫《程序員的自我修養》一書,由於水平比以前有所提升,所以讀書的收獲也不一樣。
 
 
下面針對該書3.3.3節BSS段的內容進行更細節的探討——該節內容不在本文中重復說明了,只說一下結論。對於全局變量來說,如果初始化了不為0的值,那麼該全局變量則被保存在data段,如果初始化的值為0,那麼將其保存在bss段,如果沒有初始化,則將其保存在common段,等到鏈接時再將其放入到BSS段。關於第三點不同編譯器行為會不同,有的編譯器會把沒有初始化的全局變量直接放到BSS段。
 
 
關於上面這個結論,我就不重復進行探討了,下面探討一下更細節點的問題。從上面的內容上看,盡管未初始化的全局變量有可能在編譯階段被保存在common段,但是最終還是會放到BSS段。那麼我們是否可以將未初始化的全局變量與初始為0的全局變量等同起來呢?
 
 
請看下面的代碼:
文件test1.c:
#include <stdio.h>
 
 
int init = 0;
 
 
 
void init1()
 
{
 
    if (0 == init) {
 
        init = 1;
 
        printf("init1\n");
    }
 
}
 
編譯gcc -g -c test1.c
文件test2.c:
#include <stdio.h>
 
 
int init = 1;
 
 
 
void init2()
 
{
 
    if (init) {
 
        init = 0;
 
        printf("init2\n");
    }
 
}
 
編譯gcc -g -c test2.c
文件main.c
void init1();
 
void init2();
 
 
 
 
 
int main()
 
{
 
    init1();
 
    init2();
 
 
 
    return 0;
 
}
 
編譯gcc -g -o test main.c test1.o test2.o
這時會報錯
test2.o:(.data+0x0): multiple definition of `init'
 
test1.o:/root/work/test/test1.c:4: first defined here
 
collect2: ld returned 1 exit status
 
這個報錯大家都可以接受吧?OK,那麼現在我們做一個小小的改動,將文件test1.c中的int init = 0改為int init,對於test1.c來說,這個改動不影響其邏輯,因為init如果未初始化,其值也應該是0。
 
 
當前的test1.c
#include <stdio.h>
 
 
 
int init;
 
 
 
void init1()
 
{
 
    if (0 == init) {
 
        init = 1;
 
        printf("init1\n");
 
    }
 
}
 
編譯gcc -g -c test1.c
gcc -g -Wall -o test main.c test1.o test2.o
這次即使使用-Wall打開所以warning,也沒有任何警告和錯誤,已經生成了輸出文件test。
執行test
[root@Lnx99 test]#./test
 
init2
 
 
 
好,www.2cto.com下面探討一下為什麼是這樣。第一種情況,當test1.c中的init被初始化為0時,盡管init被放置在bss段,但是它是一個強符號。而test2.c中,定義了init為1,也是一個強符號,所以引發了錯誤。第二種情況,當test1.c中的init不進行初始化,盡管其值仍然為0,但是其被保存在common段,為一個弱符號。當test2.c中定義了init為1一個強符號,那麼在鏈接的過程中,gcc會用這個強符號覆蓋掉弱符號,並不會引起鏈接沖突錯誤。但是在運行階段,進入init1時,這個init的值卻並不是其所期望的值,因此導致沒有打印init1。
 
 
我想關於BSS與COMMON段的這點不同,應該講得比較清楚了。從這個區別中,我們需要注意,當定義全局變量時,有兩點需要注意:一,如果只有本文件使用,那麼需要添加上static;二,即使不能使用static,那麼一定要為該全局變量定義初值,即使這個值就是0。這樣可以保證該變量為強符號,當名字沖突時,可以發現,而不是被未知的值覆蓋。三嘛,最好能夠避免全局變量,或者定義一個獨一無二的名字。
 
 
再多說一點,在編譯階段,還可以通過-fno-common選項來禁止將未初始化的全局變量放入到common段。
同樣的文件,看下面的編譯鏈接過程:
[root@Lnx99 test]#gcc -g -fno-common -c test1.c
 
[root@Lnx99 test]#gcc -g -fno-common -c test2.c
 
[root@Lnx99 test]#gcc -g -fno-common -o test main.c test1.o test2.o
 
test2.o:(.data+0x0): multiple definition of `init'
 
test1.o:/root/work/test/test1.c:6: first defined here
 
collect2: ld returned 1 exit status
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved