全局變量如果不初始化,則默認爲0,編譯時編譯器不提示“變量未初始化”

一、內存區域的劃分

      一個由C/C++編譯的程序佔用的內存分爲以下幾個部分:

     1)、棧區(Stack):由編譯器(Compiler)自動分配釋放,存放函數的參數值,局部變的值等。其操作方式類似於數據結構中的棧。

     2)、堆區(Heap ):一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配

             方式倒是類似於鏈表。

     3)、全局區(靜態區)(static):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全

             局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放。

     4)、文字常量區:常量字符串就是放在這裏的。程序結束後由系統釋放。

     5)、程序代碼區:存放函數體的二進制代碼。

 

二、測試案例(源碼與反彙編對照)

   

     2.1   測試案例源碼與反彙編對照

             爲了能夠形象地說明內存佈局模型,先來看一段Win32 Console Application代碼(表3.1),其中,加粗文字(行最左端爲行標號)

     爲C源代碼,未加粗文字(行最左端爲地址)爲反彙編後的指令代碼。看上去比較零亂,不過一定要耐住性子,後面的文字將基於此。

 

     

3.2   內存佈局圖

        對於該案例,以下幾幅圖形象地說明了第2節提到的內存5大區域。需要注意的是,圖中各區域的起始地址不是絕對的,不同的編譯環境可能不完全相同,這裏給出的只是一個典型示例。需要注意的是,不同區域地址編址方向也不同。

    

 

    

    

    

    

    

   

3、應用

     通過對第3節案例的理解,我們將對一些現象予以解釋。

 

3.1、變量初始化

        1)局部變量將被分配在棧中,如果不初始化,則爲不可預料值。編譯時,編譯器將拋出一個編號爲C4700警告錯誤(local variable '變量名' used without having been initialized)。

        表4.1代碼測試了局部變量未初始化的情況。

       

 

   該測試的一個典型的輸出結果爲:-858993460,同時,編譯時編譯器拋出了一條警告錯誤。

   2)全局變量如果不初始化,則默認爲0,編譯時編譯器不提示“變量未初始化”。

        表4.2代碼測試了全局變量未初始化的情況。

   該測試的輸出結果爲:0.

   3)全局變量初始化爲0與不初始化效果一樣。請留意表3.1第9行代碼,即

          intinit_array_g1[10]={0};                       //初始化的全局數組1

      等效於:

      int   init_array_g1[10];                       //初始化的全局數組1

      當然,出於謹慎,我們還是建議在使用全局變量前對其初始化。

   3.2  變量初始化對代碼空間的影響

         本小節任然討論變量初始化問題,但出於重視,我們將其獨立成小節。現在看兩個測試案例。

         案例1:建立Win32 Console Application工程,工程名:Test1,代碼如表4.3。

 

 

   編譯成Debug版本,察看Debug目錄下的Test1.exe可執行文件大小,典型大小約184KB(約0.18MB)。

   案例2:建立Win32 Console Application工程,工程名:Test2,代碼如表4.4。

 

 

   編譯成Debug版本,察看Debug目錄下的Test2.exe可執行文件大小,典型大小約46MB。

   兩個案例唯一區別不過在於是用0還是1初始化 init_array_g1[]數組第0個元素。生成的可執行文件大小卻天壤之別。

   上面已經說過,對於全局變量初始化爲0與不初始化效果一樣。因此,這裏的Test1案例並沒有對全局變量初始化。

   那麼全局變量初始化於不初始化對代碼空間又有什麼影響呢?

   我們知道,運行於基於馮·諾依曼體系結構系統上的程序,數據和程序是一起存儲了。因此,編譯時,編譯器會將全局變量的初始化數據捆綁到最終生成的程序文件中,而對於未初始化的全局變量只是爲其分配(指示)了存儲位置,不會將大量的0捆綁到程序中。

   現在再來看以上兩個案例。Test1實質上沒有初始化全局變量,編譯時編譯器只是爲了init_array_g1[]指出了將要使用的內存位置,而不發生 數據綁定。Test2則不同,它將init_array_g1[0]初始化爲1,其它元素全部初始化爲0,因此,編譯器將把 init_array_g1[]數組的10000000個元素的初始化數據全部捆綁到最終的可執行文件中,導致編譯後的文件十分龐大。

 

    3.3   關於堆和棧

    由於歷史原因,我們習慣把堆和棧合在一起稱呼(堆棧),然而,在這裏我們要嚴格區分堆和棧的概念。

    例程中聲明的局部變量被分配在棧中,而棧的大小是相當有限的(一、兩個兆),龐大的數組可能使棧不夠用,造成運行期棧溢出(Overflow)錯誤(注意:不是編譯器錯誤),而堆的大小主要取決於系統可用內存和虛存的多少。下面來看幾個例子:

    案例3代碼如表4.5所示:

   

   編譯該代碼,沒有編譯期錯誤。執行時卻發生了運行期錯誤(提示Statck Overflow),因爲棧空間不夠用。

   案例4,把案例3代碼改一下,數組定位爲全局變量,如表4.6所示:

    

     編譯該代碼,沒有編譯期錯誤,也不發生運行期錯誤。因爲全局變量不是分配在棧中的(注意:也不在堆中),能用多大空間取決於系統可用內存和虛存的多少。

     對於案例3的問題還有一種方法可以解決:動態申請內存空間。

     動態申請的內存空間是在運行期分配的,一旦申請成功,將分配在堆中,因此,大小也是取決於系統可用內存和虛存的多少。

 

     案例5:把案例3代碼用另一種方法改一下,如表4.7所示。

 

    案例5的內存空間在堆中。還有一點不同於案例4:案例4的內存空間是在編譯器分配的,而案例5的內存空間是在運行期分配的,有可能分配不到空間。

    3.4) 地址遞減編制方式

    或許其它資料中已經描述了“地址遞減”編址方式分配內存的概念,所謂“地址遞減“是指編譯器編譯程序時,按變量聲明先後,從可分配內存中從高地址向低地址分配內存。什麼意思?還是先來看一個例子。

    案例6是一個有邏輯錯誤的程序(表4.7所示),不妨稱其爲“變態”程序。那麼它是如何BT的呢?

    

 

      這個程序沒有編譯器錯誤,但卻是一個死循環程序。我們想知道的是:它爲什麼是個死循環,而不是其它什麼錯誤?通過以上文字對內存佈局的介紹,我們已經可以很容易解釋之。

      仿照第3節內容可以畫出內存佈局示意圖(如圖4.1所示,圖中起始地址只是一個典型情況)。

      注意,程序中引用了array[10]————數組下標越界(VC++6.0編譯器可以檢查出顯示的下標越界,但是不檢查隱式的下標越界)。循環內部會將 所謂的array[10]置1,而從圖4.1可知,array[10]實質上就是i,導致程序最終死循環也就理所當然了。

      一切變得明朗起來,我們不僅解釋了程序中的問題,同時還明白了“地址遞減”編址方式並不神祕,它原來就是我們前面提到的棧內存區的編址方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章