1. 程式人生 > >C語言記憶體分配及各種資料儲存位置

C語言記憶體分配及各種資料儲存位置

版本宣告:本文轉載於公眾號TeachPlus

指標的使用,一直是c語言面試題中必考的部分,

因為指標本身使用的複雜性與普適性,所以考點非常多,而且也可以與其他知識相互結合,

因此我們將會使用五篇專題的篇幅來介紹指標。分析下面的程式,指出程式中的錯誤:

  1. # include <stdio.h>
  2. int  main( void)  
  3. {  
  4.     char a;  
  5.     char *str=&a;  
  6.     strcpy(str,"hello");  
  7.     printf("%s\n",str);  
  8.     return 0;  
  9. }  

本題解析

沒有正確為str分配記憶體空間,將會發生異常。

問題出在將一個字元串複製進一個字元變量指標所指地址。

雖然編譯的時候沒有報錯,但是在運行過程中,因為越界訪問了未被分配的記憶體,而導致段錯誤。

相關知識點

在處理與指標相關的問題時,首先需要搞明白的就是記憶體,因為指標操作的就是記憶體。
第一個,就是記憶體的分割槽。這也是經常會被考察的一個考點。

寫出記憶體分為幾大區域

對於這個問題,有幾種不不同的說法。

有的說記憶體分為五大分割槽,有的說分為四大分割槽,我們先來先看五個分割槽的說法:

認為記憶體分為五大分割槽的人,通常會這樣劃分:

1、BSS段( bss segment )

通常是指用來存放程式中未初始化的全域性變量和靜態變量 (這里注意一個問題:一般的

書上都會說全域性變量和靜態變量是會自動初始化的,那麼哪來的未初始化的變量呢?變量的

初始化可以分為顯示初始化和隱式初始化,全域性變量和靜態變量如果程式設計師自己不初始化的話的確

也會被初始化,那就是不管什麼型別都初始化為0,這種沒有顯示初始化的就是我們這里所說的未初始化。

既然都是0那麼就沒必要把每個0都儲存起來,從而節省磁碟空間,這是BSS的主要作用)的一塊記憶體區域。

BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態記憶體分配。 

BSS節不不包含任何資料,只是簡單的維護開始和結束的地址,即總大小。

以便記憶體區能在執行時分配並被有效地清零。BSS節在應用程式的二進位制映象檔案中並不存在,

即不佔用磁碟空間而只在運行的時候佔用記憶體空間 ,所以如果全域性變量和靜態變量未初始化那麼其可執行檔案要小很多。

2、資料段(data segment)

通常是指用來存放程式中已經初始化的全域性變量和靜態變量的一塊記憶體區域。

資料段屬於靜態記憶體分配,可以分為只讀資料段和讀寫資料段。

字元串常量等,但一般都是放在只讀資料段中。

3、程式碼段(code segment/text segment)

通常是指用來存放程式執行程式碼的一塊記憶體區域。

這部分割槽域的大小在程式運行前就已經確定,

並且記憶體區域通常屬於只讀, 某些架構也允許程式碼段為可寫,即允許修改程式。

在程式碼段中,也有可能包含一些只讀的常數變量,例例如字元串常量等,但一般都是放在只讀資料段中 。

4、堆(heap)

堆是用於存放程序運行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。

當程序呼叫malloc等函式分配記憶體時,新分配的記憶體就被動態新增到堆上(堆被擴張); 

當利用free等函式釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減)

5、棧 (stack)

棧又稱堆疊, 是使用者存放程式臨時建立的區域性變量,也就是說我們函式括弧“{}” 中定義

的變量(但不不包括static宣告的變量,static意味著在資料段中存放變量)。

除此以外, 在函式被呼叫時,其引數也會被壓入發起呼叫的程序棧中,並且待到呼叫結束後,

函式的返回值也會被存放回棧中。由於棧的先進先出特點,所以 棧特別方便用來儲存/恢復呼叫現場。

從這個意義上講,我們可以把堆疊看成一個寄存、交換臨時資料的記憶體區。

而四大分割槽的說法,則這麼認為:

1、堆區:

由程式設計師手動申請,手動釋放,若不手動釋放,程式結束後由系統回收,生命週期是整個程式運

行期間。使用malloc或者new進行堆的申請,堆的總大小為機器器的虛擬記憶體的大小。

說明:new操作符本質上是使用了malloc進行記憶體的申請,new和malloc的區別如下:

(1)malloc是C語言中的函式,而new是C++中的操作符。

(2)malloc申請之後返回的型別是void*,而new返回的指標帶有型別。

(3)malloc只負責記憶體的分配而不會呼叫類的建構函式,而new不僅會分配記憶體,

而且會自動呼叫類的建構函式。

2、棧區:

由系統進行記憶體的管理理。主要存放函式的引數以及區域性變量。

在函式完成執行,系統自行釋放棧區記憶體,不需要使用者管理。

整個程式的棧區的大小可以在編譯器器中由使用者自行設定,

VS中預設的棧區大小為1M,可通過VS手動更改棧的大。

64bits的Linux預設棧大小為10MB,可通過ulimit-s臨時修改。

3、靜態儲存區:

靜態儲存區內的變量在程式編譯階段已經分配好記憶體空間並初始化。這塊記憶體在程式的整個執行

期間都存在,它主要存放靜態變量、全域性變量和常量。

注意:
(1)這里不區分初始化和未初始化的資料區,是因為靜態儲存區內的變量若不顯示初始化,

則編譯器會自動以預設的方式進行初始化,

即靜態儲存區內不存在未初始化的變量。

(2)靜態儲存區內的常量分為常變量和字串常量,一經初始化,不可修改。

靜態儲存內的常變量是全域性變量,與區域性常變量不不同,

區別在於區域性常變量存放於棧,實際可間接通過指標或者

引用進行修改,而全域性常變量存放於靜態常量區則不可以間接修改。

(3)字元串常量儲存在靜態儲存區的常量區,字元串常量的名稱即為它本身,屬於常變量。

(4)資料區的具體劃分,有利利於我們對於變量型別的理理解。

不同類型的變數存放的區域不同。後面將以實例程式碼說明這四種資料區中具體對應的變量。

4、程式碼區:

存放程式體的二進位制程式碼。比如我們寫的函式,都是在程式碼區的。


通過上面的不同說法,我們也可以看出,這兩種說法本身沒有優劣之分,

具體的記憶體劃分也跟編譯器有很大的關係,因此這兩種說法都是可以接受的,

搞明白記憶體的分割槽之後,指標的使用才能夠更更加的靈活