1. 程式人生 > >C/C++堆、棧及靜態資料區詳解(轉載只是為了查閱方便,若侵權立刪)

C/C++堆、棧及靜態資料區詳解(轉載只是為了查閱方便,若侵權立刪)

C/C++堆、棧及靜態資料區詳解

  本文介紹C/C++中堆,棧及靜態資料區。

 

  五大記憶體分割槽

  在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。下面分別來介紹:

  棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。

 

  堆,就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new就要對應一個delete。如果程式設計師沒有釋放掉,那麼在程式結束後,作業系統會自動回收。

 

  自由儲存區,就是那些由malloc等分配的記憶體塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。

 

  全域性/靜態儲存區,全域性變數和靜態變數被分配到同一塊記憶體中,在以前的C語言中,全域性變數又分為初始化的和未初始化的,在C++裡面沒有這個區分了,他們共同佔用同一塊記憶體區(未初始化的變數都被初始化成0或空串,C中也一樣)。

 

  常量儲存區,這是一塊比較特殊的儲存區,他們裡面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改,而且方法很多)。

 

  明確區分堆與棧:

  在bbs上,堆與棧的區分問題,似乎是一個永恆的話題,由此可見,初學者對此往往是混淆不清的,所以我決定拿他第一個開刀。

  首先,我們舉一個例子:

void f() { int* p=new int[5]; }

 

  上面這條短短的一句話就包含了堆與棧,看到new,我們首先就應該想到,我們分配了一塊堆記憶體,那麼指標p呢?他分配的是一塊棧記憶體,所以這句話的意思 就是:在棧記憶體中存放了一個指向一塊堆記憶體的指標p。在程式會先確定在堆中分配記憶體的大小,然後呼叫operator new分配記憶體,然後返回這塊記憶體的首地址,放入棧中,他在VC6下的彙編程式碼如下:

複製程式碼

00401028 push 14h

0040102A call operator new (00401060)

0040102F add esp,4

00401032 mov dword ptr [ebp-8],eax

00401035 mov eax,dword ptr [ebp-8]

00401038 mov dword ptr [ebp-4],eax

複製程式碼

 

  這裡,我們為了簡單並沒有釋放記憶體,那麼該怎麼去釋放呢?是delete p麼?哦,錯了,應該是delete []p,這是為了告訴編譯器:我刪除的是一個數組,VC6就會根據相應的Cookie資訊去進行釋放記憶體的工作。

 

  好了,我們回到我們的主題:堆和棧究竟有什麼區別?

 

  主要的區別由以下幾點:

  1、管理方式不同;

  2、空間大小不同;

  3、能否產生碎片不同;

  4、生長方向不同;

  5、分配方式不同;

  6、分配效率不同;

 

  管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式設計師控制,容易產生memory leak。

 

  空間大小:一般來講在32位系統下,堆記憶體可以達到4G的空間,從這個角度來看堆記憶體幾乎是沒有什麼限制的。但是對於棧來講,一般都是有一定的空間大小的,例如,在VC6下面,預設的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改:

  開啟工程,依次操作選單如下:Project->Setting->Link,在Category 中選中Output,然後在Reserve中設定堆疊的最大值和commit。

 

  注意:reserve最小值為4Byte;commit是保留在虛擬記憶體的頁檔案裡面,它設定的較大會使棧開闢較大的值,可能增加記憶體的開銷和啟動時間。

 

  碎片問題:對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個 問題,因為棧是先進後出的佇列,他們是如此的一一對應,以至於永遠都不可能有一個記憶體塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出, 詳細的可以參考資料結構,這裡我們就不再一一討論了。

 

  生長方向:對於堆來講,生長方向是向上的,也就是向著記憶體地址增加的方向;對於棧來講,它的生長方向是向下的,是向著記憶體地址減小的方向增長。

 

  分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由alloca函式進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。

 

  分配效率:棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的 效率比較高。堆則是C/C++函式庫提供的,它的機制是很複雜的,例如為了分配一塊記憶體,庫函式會按照一定的演算法(具體的演算法可以參考資料結構/操作系 統)在堆記憶體中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於記憶體碎片太多),就有可能呼叫系統功能去增加程式資料段的記憶體空間,這樣就 有機會分到足夠大小的記憶體,然後進行返回。顯然,堆的效率比棧要低得多。

  

  從這裡我們可以看到,堆和棧相比,由於大量 new/delete的使用,容易造成大量的記憶體碎片;由於沒有專門的系統支援,效率很低;由於可能引發 使用者態和核心態的切換,記憶體的申請,代價變得更加昂貴。所以棧在程式中是應用最廣泛的,就算是函式的呼叫也利用棧去完成,函式呼叫過程中的引數,返回地 址,EBP和區域性變數都採用棧的方式存放。所以,我們推薦大家儘量用棧,而不是用堆。

 

  雖然棧有如此眾多的好處,但是由於和堆相比不是那麼靈活,有時候分配大量的記憶體空間,還是用堆好一些。

 

  無論是堆還是棧,都要防止越界現象的發生(除非你是故意使其越界),因為越界的結果要麼是程式崩潰,要麼是摧毀程式的堆、棧結構,產生以想不到的結 果,就算是在你的程式執行過程中,沒有發生上面的問題,你還是要小心,說不定什麼時候就崩掉,那時候debug可是相當困難的:)

 

  對了,還有一件事,如果有人把堆疊合起來說,那它的意思是棧,可不是堆,呵呵,清楚了?

 

  static用來控制變數的儲存方式和可見性

  函式內部定義的變數,在程式執行到它的定義處時,編譯器為它在棧上分配空間,函式在棧上分配的空間在此函式執行結束時會釋放掉,這樣就產生了一個問 題: 如果想將函式中此變數的值儲存至下一次呼叫時,如何實現? 最容易想到的方法是定義一個全域性的變數,但定義為一個全域性變數有許多缺點,最明顯的缺點是破壞了此變數的訪問範圍(使得在此函式中定義的變數,不僅僅受此 函式控制)。

  需要一個數據物件為整個類而非某個物件服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見。

 

  static的內部機制:

  靜態資料成員要在程式一開始執行時就必須存在。因為函式在程式執行中被呼叫,所以靜態資料成員不能在任何函式內分配空間和初始化。

 

  這樣,它的空間分配有三個可能的地方,一是作為類的外部介面的標頭檔案,那裡有類宣告;二是類定義的內部實現,那裡有類的成員函式定義;三是應用程式的main()函式前的全域性資料宣告和定義處。

 

  靜態資料成員要實際地分配空間,故不能在類的宣告中定義(只能宣告資料成員)。類宣告只宣告一個類的“尺寸和規格”,並不進行實際的記憶體分配,所以在類宣告中寫成定義是錯誤的。它也不能在標頭檔案中類宣告的外部定義,因為那會造成在多個使用該類的原始檔中,對其重複定義。

  

static被引入以告知編譯器,將變數儲存在程式的靜態儲存區而非棧上空間,靜態資料成員按定義出現的先後順序依次初始化,注意靜態成員巢狀時,要保證所巢狀的成員已經初始化了。消除時的順序是初始化的反順序。

 

  static的優勢:

  可以節省記憶體,因為它是所有物件所公有的,因此,對多個物件來說,靜態資料成員只儲存一處,供所有物件共用。靜態資料成員的值對每個物件都是一樣,但它的值是可以更新的。只要對靜態資料成員的值更新一次,保證所有物件存取更新後的相同的值,這樣可以提高時間效率。

 

  引用靜態資料成員時,採用如下格式:

  <類名>::<靜態成員名>

  如果靜態資料成員的訪問許可權允許的話(即public的成員),可在程式中,按上述格式來引用靜態資料成員。

  PS:

  (1)類的靜態成員函式是屬於整個類而非類的物件,所以它沒有this指標,這就導致了它僅能訪問類的靜態資料和靜態成員函式。

  (2)不能將靜態成員函式定義為虛擬函式。

  (3)由於靜態成員聲明於類中,操作於其外,所以對其取地址操作,就多少有些特殊,變數地址是指向其資料型別的指標,函式地址型別是一個“nonmember函式指標”。

  (4)由於靜態成員函式沒有this指標,所以就差不多等同於nonmember函式,結果就產生了一個意想不到的好處:成為一個callback函式,使得我們得以將C++和C-based X Window系統結合,同時也成功的應用於執行緒函式身上。

  (5)static並沒有增加程式的時空開銷,相反她還縮短了子類對父類靜態成員的訪問時間,節省了子類的記憶體空間。

  (6)靜態資料成員在<定義或說明>時前面加關鍵字static。

  (7)靜態資料成員是靜態儲存的,所以必須對它進行初始化。

  (8)靜態成員初始化與一般資料成員初始化不同:

 

  初始化在類體外進行,而前面不加static,以免與一般靜態變數或物件相混淆;

  初始化時不加該成員的訪問許可權控制符private,public等;

  初始化時使用作用域運算子來標明它所屬類;

  所以我們得出靜態資料成員初始化的格式:

  <資料型別><類名>::<靜態資料成員名>=<值>

 

  (9) 為了防止父類的影響,可以在子類定義一個與父類相同的靜態變數,以遮蔽父類的影響。這裡有一點需要注意:我們說靜態成員為父類和子類共享,但 我們有重複定義了靜態成員,這會不會引起錯誤呢?不會,我們的編譯器採用了一種絕妙的手法:name-mangling 用以生成唯一的標誌。

  -------------------------------------------------------------------------------------------------------------------------------------------

  

  C語言變數的儲存類別

   記憶體中供使用者使用的儲存空間分為程式碼區與資料區兩個部分。變數儲存在資料區,資料區又可分為靜態儲存區與動態儲存區。 

  靜態儲存是指在程式執行期間給變數分配固定儲存空間的方式。如全域性變數存放在靜態儲存區中,程式執行時分配空間,程式執行完釋放。 

  動態儲存是指在程式執行時根據實際需要動態分配儲存空間的方式。如形式引數存放在動態儲存區中,在函式呼叫時分配空間,呼叫完成釋放。 

  對於靜態儲存方式的變數可在編譯時初始化,預設初值為O或空字元。對動態儲存方式的變數如不賦初值,則它的值是一個不確定的值。 

  在C語言中,具體的儲存類別有自動(auto)、暫存器(register)、靜態(static)及外部(extern)四種。靜態儲存類別與外部儲存類別變數存放在靜態儲存區,自動儲存類別變數存放在動態儲存區,暫存器儲存類別直接送暫存器。 

  

  變數儲存類別定義方法: 
  儲存類別型別變量表; 
  例如: 
  (1)a,b,c為整型自動儲存類別變數: 
  auto int a,b,c; 
  (2)x,y,z為雙精度型靜態儲存類別變數:   
  static double x,y,z;

 

  1、變數有哪些儲存型別? 
  變數的儲存型別由“儲存型別指明符”來說明。儲存型別指明符可以是下列類鍵字之一: 
  auto 
  register 
  extern 
  static

 

  下面是詳細的解釋: 
  auto 儲存類指明符--用於說明具有區域性作用域的變數,它表示變數具有區域性(自動)生成期,但由於它是所有區域性作用域變數說明的預設儲存類指明符,所以使用得很 少。要注意的是,所有在函式內部定義的變數都是區域性變數,函式內部定義的變數其作用域只在函式內部。它的生存期為該函式執行期間,一旦離開這個函式或這個 函式終止,區域性變數也隨之消失。


  register 儲存類指明符--當聲明瞭這個指明符後,編譯程式將盡可能地為該變數分配CPU內部的暫存器作為變數的儲存單元,以加快執行速度。注意,暫存器與儲存器是 不同的。暫存器一般在CPU內部,而儲存器一般指外部的(比如記憶體條),CPU內部的暫存器其運算速度是很高的。當暫存器已分配完畢,就自動地分配一個外 部的記憶體。它的作用等價於auto,也只能用於區域性變數和函式的參量說明。


  static 儲存類指明符--表示變數具有靜態生成期。static變數的的特點是它離開了其作用域後,其值不會消失。


  當回到該作用域之後又可以繼續使用這個static變數的值。


  例:利用static變數統計呼叫函式的次數

複製程式碼

int two(); /*函式原型說明*/ 
void main() 
{ 
int a=0; 
a=two(); /*a的值等於1*/ 
a=two() /*a的值等於2*/ 
a=two(); /*a的值等於3*/ 
}

int two() 
{ 
static int b=0;    /*定義了一個區域性的static變數*/ 
b ; 
return b; 
}

複製程式碼

 

  如果不是一個static變數就不會有這個效果了 

複製程式碼

int two(); /*函式原型說明*/ 
void main() 
{ 
int a=0; 
a=two(); /*a的值等於1*/ 
a=two() /*a的值等於1*/ 
a=two(); /*a的值等於1*/ 
}

int two() 
{ 
int b=0;     
b ; 
return b; 
} 

複製程式碼

 

  變數a的值總是1,原因是在函式two()中,變數b不是一個static變數,其值隨著離開two函式就消失了,當回到two函式時又被重新賦值0。

 

  extern 儲存類指明符--一般用在工程檔案中。在一個工程檔案中因為有多個程式檔案,當某一個變數在一個程式檔案中定義了之後,如果在另一個程式檔案中予以定義, 就會出現重複定義變數的錯誤。使用extern儲存型別指明符就可以指出在該檔案外部已經定義了這個變數。extern變數的作用域是整個程式。

 

  2、變數儲存在記憶體的什麼地方?

  1)變數可以儲存在記憶體的不同地方,這依賴於它們的生成期。在函式上部定義的變數(全域性變數或static外部變數)和在函式內部定義的static變 量,其生存期就是程式執行的全過程。這些變數被儲存在資料段(Data Segment)中。資料段是在記憶體中為這些變數留出的一段大小固定的空間,它分 為二部分,一部分用來初始化變數,另一部分用來存放未初始化的變數。

  2)在函式內部定義的auto變數(沒有用關鍵字static定義的變數)的生成期從程式開始執行其所在的程式塊程式碼時開始,到程式離開該程式塊時為止。 作為函式引數的變數只在呼叫該函式期間存在。這些變數被儲存在棧(stack)中。棧是記憶體中的一段空間,開始很小,以後逐漸自動變大,直到達到某個預定 義的界限。

  3)當用malloc等函式給指標分配一個地址空間的時候,這個分配的記憶體塊位於一段名為“堆(heap)”的記憶體空間中。堆開始時很小,但呼叫 malloc或clloc等記憶體分配函式時它就會增大。堆可以和資料段或棧共用一個記憶體段,也可以有它自己的記憶體段,這完全取決於編譯選項和作業系統。與 棧相似,堆也有一個增長界限,並且決定這個界限的規則與棧相同。

  -----------------------------------------------------------------------------------------------------------------------------------------------------------------

  C語言變數的作用域和儲存型別
  一、作用域和生存期


      C程式的識別符號作用域有三種:區域性、全域性、檔案。識別符號的作用域決定了程式中的哪些語句可以使用它,換句話說,就是識別符號在程式其他部分的可見性。通常,識別符號的作用域都是通過它在程式中的位置隱式說明的。

  

  1.區域性作用域

  前面各個例子中的變數都是區域性作用域,他們都是宣告在函式內部,無法被其他函式的程式碼所訪問。函式的形式引數的作用域也是區域性的,它們的作用範圍僅限於函式內部所用的語句塊。

複製程式碼

      void add(int);

          main()
          {
              int num=5;
              add(num);
              printf("%d\n",num);       /*輸出5*/
          }

          void add(int num)
          {
              num++;
              printf("%d\n",num);       /*輸出6*/
          }

複製程式碼

 

  上面例子裡的兩個num變數都是區域性變數,只在本身函式裡可見。前面我們說了,在兩個函數出現同名的變數不會互相干擾,就是這個道理。所以上面的兩個輸出,在主函式裡仍然是5,在add()函式裡輸出是6。

 

  2.全域性作用域

  對於具有全域性作用域的變數,我們可以在程式的任何位置訪問它們。當一個變數是在所有函式的外部宣告,也就是在程式的開頭宣告,那麼這個變數就是全域性變數。

複製程式碼

      void add(int);
          int num;

          main()
          {
              int n=5;
              add(n);
              printf("%d\n",num);      /*輸出6*/
          }

          void add(num)          /*形式引數沒有指定型別*/
          {
              num++;
              printf("%d\n",num);      /*輸出6*/
          }

複製程式碼

 

      上面的main()和add()裡面,並沒有宣告num,但是在最後輸出的時候卻要求輸出num,這是由於在程式的開始聲明瞭num是全域性變數,也就是在 所有函式裡都可以使用這個變數。這時候一個函式裡改變了變數的值,其他函式裡的值也會出現影響。上面的例子輸出都是6,因為在add()函式裡改變了 num的值,由於num是全域性變數,就好象它們兩個函式共用一個變數,所以在main()函式裡的num也隨之改變了。

  

  3.檔案作用域

  在很多C語言書上,都沒有說明檔案作用域,或者只是略微的提到,其實檔案作用域在較大程式中很有作用(在多檔案系統中)。檔案作用域是指外部識別符號僅在聲 明它的同一個轉換單元內的函式彙總可見。所謂轉換單元是指定義這些變數和函式的原始碼檔案(包括任何通過#i nclude指令包含的原始碼檔案)。static儲存型別修飾符指定了變數具有檔案作用域。

複製程式碼

       static int num;
          static void add(int);

          main()
          {
              scanf("%d",&num);
              add(num)
              printf("%d\n",num);
          }

          void add(num)
          {
              num++;
          }

複製程式碼

 

     上面的程式中變數num和函式add()在宣告是採用了static儲存型別修飾符,這使得它們具有檔案作用域,僅愛定義它們的檔案內可見。


     由於我們提到的大多數程式都只有一個編譯檔案組成,所以這種寫法沒有實際意義。但是實際工程上的檔案有很多,它們不是由一個人寫成的,由很多人共同完成, 這些檔案都是各自編譯的,這難免使得某些人使用了一樣的全域性變數名,那麼為了以後程式中各自的變數和函式不互相干擾,就可以使用static修飾符,這樣 在連線到同一個程式的其他程式碼檔案而言就是不可見的。

 

  二、變數儲存型別

  前面我們說了,宣告變數時用如下類似的形式:

int num;
float total;

 

   它們都沒有儲存型別修飾符,我們在宣告時也可以通過儲存型別修飾符來告訴編譯器將要處理什麼型別的變數。儲存型別有以下四種:自動(auto)、靜態(static)、外部(extern)、暫存器(regiser)。

      1.自動儲存型別

  自動儲存型別修飾符指定了一個區域性變數為自動的,這意味著,每次執行到定義該變數的語句塊時,都將會為該變數在記憶體中產生一個新的拷貝,並對其進行初始化。實際上,如果不特別指明,區域性變數的儲存型別就預設為自動的,因此,加不加auto都可以。

  main()
   {
         auto int num=5;
         printf("%d\n",num);
   }

 

  在這個例子中,不論變數num的宣告是否包含關鍵字auto,程式碼的執行效果都是一樣的。函式的形式引數儲存型別預設也是自動的。
        
      2.靜態儲存變數

  前面已經使用了static關鍵字,但是對於區域性變數,靜態儲存型別的意義是不一樣的,這時,它是和自動儲存型別相對而言的。靜態區域性變數的作用域仍然近 侷限於宣告它的語句塊中,但是在語句塊執行期間,變數將始終保持它的值。而且,初始化值只在語句塊第一次執行是起作用。在隨後的執行過程中,變數將保持語 句塊上一次執行時的值。

  看下面兩個對應的程式:

複製程式碼

       /*1.C*/                                /*2.C*/
            int add();                             int add();

            main()                                 main()
            {                                      {
                int result;                            int result;
                result=add()                           result=add();
                printf("%d ",result);                  printf("%d ",result);
                result=add();                          result=add();
                printf("%d ",result);                  printf("%d ",result);
                result=add();                          result=add();
                printf("%d",result);                   printf("%d",result);
            }                                      }

            int add()                              int add()
            {                                      {
                int num=50;                            static int num=50;
                num++;                                 num++;
                return num;                            return num;
            }                                      }

複製程式碼

 

  上面兩個原始檔,只有函式add()裡的變數宣告有所不同,一個是自動儲存型別,一個是靜態儲存型別。

  對於1.C檔案,輸出結果為51 51 51;這很好理解,每次初始值都是50,然後加1上來。

  對於2.C檔案,輸出結果為51 52 53;這是由於變數是靜態的,只在第一次初始化了50,以後都是使用上次的結果值。當第一次呼叫add()時,初始化為50,然後加1,輸出為51;當第 二次呼叫時,就不初始化了,這時num的值為上次的51,然後加1,輸出52;當第三次呼叫時,num為52,加1就是53了。

  比較就會發現它們的不同之處了。靜態變數在下一節要說的遞迴函式中經常使用到。

  當第一次不指明靜態變數的初始值時,預設為0。

  下面舉一個例子,把我們說到的靜態變數理解一下。

  求1+2+……+100的值的程式碼如下:

複製程式碼

      void add();
           int result;

           main()
           {
               int i;
               result=0;
               for(i=0;i<100;i++) add();
               printf("%d\n",result);
           }

           void add()
           {
               static int num=0;
               num++;
               result+=num;
           }

複製程式碼

  

   add()函式被呼叫了100次,num的值從1一直變到100,這樣就可以求出它們的和了。如果寫成int num=0;那就是求1+1+……+1這100個1的值了。

   實際上類似的這類問題我們可以通過遞迴函式來解決,什麼是遞迴,我們下一節介紹。

      3.外部儲存型別

  外部儲存型別聲明瞭程式將要用到的、但尚未定義的外部變數。通常,外部儲存型別都是用於宣告在另一個轉換單元中定義的變數。下面舉一個例子,這個例子包括兩個檔案。

複製程式碼

         /*1.C*/
              void a();

              main()
              {
                  extern int num;
                  a();
                  printf("%d\n",num);
              }

               /*2.C*/
              int num;

              void a()
              {
                  num=5;
              }

複製程式碼

 

  這兩個程式是分別編譯的,然後連線成一個執行檔案。具體如何操作,可以檢視一些手冊,這兒我簡單說了一下。把上面兩個檔案都編譯好後,再製作一個.prj檔案,裡面的內容是:

  1.c
  2.c


  只有這兩行,這可在編輯狀態下寫成,存檔,取名為1.prj。
  然後選擇project選項,選擇project name,填入1.prj檔名,按F9後,即可生成1.exe檔案。

  

  main()函式中變數num是在另一個檔案中定義的。因此,當編譯器編譯1.c時,無法確定該變數的地址。這時,外部儲存型別宣告告訴編譯器,把所有對 num的引用當作暫且無法確定的引用,等到所有便宜好的目的碼連線成一個可執行程式模組時,再來處理對變數num的引用。

  

  外部變數的宣告既可以在引用它的函式的內部,也可以在外部。如果變數宣告在函式外部,那麼同一轉換單元內的所有函式都可以使用這個外部變數。反之,如果在函式內部,那麼只有這一個函式可以使用該變數。

  前面說了檔案作用域的問題,如果在宣告全域性變數時,加上static修飾符,那麼該變數只在當前檔案內可見,而extern又可以引用其它檔案裡的變數。 所以在一個大型程式中,每個程式設計師只是完成其中的一小塊,為了讓自己的變數不讓其他程式設計師使用,保持一定的獨立性,經常在全域性變數前加static。我們可以這樣來說明一下:

  還是上面的兩個檔案,現在再增加一個檔案3.c,內容為:

  static int num;

   void a()
   {
        num=6;
   }

 

   把1.prj檔案後面加上3.c 這樣,我們生成的1.exe檔案,執行時輸出是5,而不是6。因為3.c檔案的num變數增加了檔案作用域,在其他檔案中是無法使用它的。

  4.暫存器儲存型別

  被宣告為暫存器儲存型別的變數,除了程式無法得到其地址外,其餘都和自動變數一樣。至於什麼是變數地址,以後說指標時會詳細介紹。

  main()
  {
      egister int num;
      num=100;
      printf("%d",num);
  }

 

  使用暫存器儲存型別的目的是讓程式設計師指定某個區域性變數存放在計算機的某個硬體暫存器裡而不是記憶體中,以提高程式的執行速度。不過,這只是反映了程式設計師的主觀意願,編譯器可以忽略暫存器儲存型別修飾符。

      暫存器變數的地址是無法取得的,因為絕大多數計算機的硬體暫存器都不佔用記憶體地址。而且,即使編譯器忽略暫存器型別修飾符把變數放在可設定地址的記憶體中,我們也無法取地址的限制仍然存在。

  要想有效的利用暫存器儲存型別,必須象組合語言程式設計師那樣瞭解處理器的內部構造,知道可用於存放變數的暫存器的數量和種類,以及他們是如何工作的。但是, 不同計算機在這些細節上未必是一樣的,因此對於一個可移植的程式來說,暫存器儲存型別的作用不大。特別是現在很多編譯器都能提供很好的優化效果,遠比程式 員來選擇有效的多。不過,暫存器儲存型別還是可以為優化器提供重要的參考。

  C的作用域還有一種,靜態塊。比如:

複製程式碼

/* 靜態塊作用域 */
{
...;
...;
}

/* 函式作用域 */
main()
{
...;
}

複製程式碼

 

  棧

  由編譯器自動分配釋放管理。區域性變數及每次函式呼叫時返回地址、以及呼叫者的環境資訊(例如某些機器暫存器)都存放在棧中。新被呼叫的函式在棧上為其自動和臨時變數分配儲存空間。通過以這種方式使用棧,C函式可以遞迴呼叫。

 

  堆

  需要由程式設計師分配釋放管理,若程式設計師不釋放,程式結束時可能由OS回收。通常在堆中進行動態儲存分配。


  非初始化資料段

  通常將此段稱為b s s段,這一名稱來源於早期彙編程式的一個操作符,意思是“block started by symbol(由符號開始的塊)”,未初始化的全域性變數和靜態變數存放在這裡。在程式開始執行之前,核心將此段初始化為0。函式外的說明:long sum[1000] ; 使此變數存放在非初始化資料段中。

  

  初始化的資料

  通常將此段稱為資料段,它包含了程式中需賦初值的變數。初始化的全域性變數和靜態變數存放在這裡。例如,C程式中任何函式之外的說明:int maxcount = 99; 使此變數以初值存放在初始化資料段中。

  

  正文段

  CPU執行的機器指令部分。通常,正文段是可共享的,所以即使是經常環境指標環境表環境字串執行的程式(如文字編輯程式、C編譯程式、s h e l l等)在儲存器中也只需有一個副本,另外,正文段常常是隻讀的,以防止程式由於意外事故而修改其自身的指令。


  對於x86處理器上的Linux,正文段從0x08048000單元開始,棧底在0xC0000000之下開始(棧由高地址向低地址方向增長)。堆頂和棧底之間未用的虛擬空間很大。


  Shell的size命令可以看到一個程式的正文段(text)、資料段(data)、非初始化資料段(bss)及檔案長度.

[[email protected]:01:49 ]$size mydesign

   text    data     bss     dec     hex filename

79210    1380     404   80994   13c62 mydesign

 

  關於C/C++堆、棧及靜態資料區詳解就講解到這裡。

 

  最後,希望轉載的朋友能夠尊重作者的勞動成果,加上轉載地址:http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html  謝謝。

 

  完畢。^_^