1. 程式人生 > >深入理解C指標學習筆記(一)

深入理解C指標學習筆記(一)

第一章 認識指標

指標在C和C++中隨處可見,它給程式設計師帶來了極大的方便,指標為動態記憶體的分配提供了重要支援。使用指標我們可以方便的操控記憶體,提高程式的執行效率;同時利用指標變數我們可以實現各種資料結構,例如連結串列等;另一方面指標與陣列的表示法密切相連,指向函式的指標也為程式中的流控制提供了更多的選擇。
指標的基本概念和簡單,就是一個存放記憶體地址的變數,所以理解指標的關鍵在於理解C程式如何管理記憶體。指標包含的是記憶體地址,不理解記憶體的組織和管理方式,就很難理解指標的工作方式。牢牢掌握了C語言程式的記憶體及其組織方式,理解指標就容易的多。

1.1C語言中記憶體的組織和管理方式

(預備知識) C語言可執行程式的記憶體情況

首先Ubuntu環境下生成可執行檔案a.out

gcc sample.c&&./a.out

然後使用Linux的size命令來檢視可執行檔案的記憶體分配和管理形式

可以看出,此執行程式在儲存時分為程式碼段(text),資料段(data)和未初始化資料段(bss)(上述dec和hex分別是此執行檔案所佔的記憶體總大小的十進位制和十六進位制表示)

  • 程式碼段(text):存放CPU執行的機器指令。通常情況下,程式碼段時可以共享的,因為對於頻繁被執行的程式,只需要在記憶體中由一份程式碼即可。程式碼段通常是隻讀的
    ,使其只讀的原因是防止程式意外的修改它的指令。另外,程式碼區還規劃了局部變數的資訊。
  • 資料段/全域性初始化資料取和靜態資料區(data):該區包含了程式中被初始化的全域性變數,靜態變數(全域性靜態變數和區域性靜態變數),靜態函式和常數資料如字串常量
  • 為初始化資料取(bss).次區域儲存的是全域性未初始化的變數。BSS區中的資料在程式開始執行前被核心初始化為0或控指標。例如

    int array[20000];
    char* myString;

    如上中的array和myString都是為初始化的全域性變數,所以儲存在bss區

C語言中正在執行的程式的記憶體管理與分配

一個正在執行的C程式程式碼區所佔用的記憶體分為程式碼區,初始化全域性變數或靜態變數區,未初始化全域性變數區,堆區和棧區五個部分。
其中五種記憶體的分配方式如圖所示:

  • 程式碼區(text segment):程式碼區的指令根據程式設計流程依次執行,對於順序指令,則只會執行一次(每個程序),如果反覆,則需要使用跳轉指令,如果進行遞迴,則需要藉助棧來實現。
  • 初始化全域性變數或靜態變數區(資料區):這裡的資料只初始化一次。
  • 未初始化資料取(BBS):它的值在執行是改變
  • 棧區(stack):這部分的記憶體由編譯器自動管理,記憶體由編譯器分配,也由編譯器釋放。棧中存放的是函式的引數值,區域性變數值等。它的操作方式類似於資料結構中的棧。每當一個函式被呼叫,該函式返回地址和一些關於被呼叫的資訊,比如某些暫存器的內容,被儲存到棧區,然後這個被呼叫的函式再為它的自動變數和臨時變數在棧區上分配空間,這就是C實現函式遞迴呼叫的原理。每執行一次函式的遞迴呼叫,一個新的棧框架就會被使用,這樣這個新例項棧裡的變數就不會uhe該函式的另一個例項棧中的變數混淆。它的生長方式為高地址到低地址
  • 堆區(heap):堆是用來進行動態記憶體分配的記憶體區域。堆記憶體的位置處於BSS和棧區之間,一般由程式設計師分配和釋放,若程式設計師忘記回收這部分記憶體,程式結束的時候會被作業系統自動收回。

程式在執行過程中分為多種記憶體型別的原因:
一個重要的原因是程式碼中不同的部分(例如程式碼和資料)的訪問次數不同,將其放在不同的區域可以節省記憶體以及訪問時間

  • 程式碼是根據流程依次執行的,只需要訪問一次,當然跳轉和遞迴有可能使程式碼執行多次,而資料一般要訪問多次,因此單獨開闢空間方便訪問和節約空間。
  • 臨時資料及需要再次使用的程式碼在執行的時候放到棧裡面,生命週期較短。
  • 全域性資料和靜態資料有可能在整個程式執行的過程中都需要訪問,因此單獨儲存管理。
  • 堆記憶體由使用者自由分配和使用,放在一個特定的記憶體區域方便管理。

堆和棧的區別

  • 管理方式不同
    棧由編譯器自動管理,無需程式設計師手工控制;而堆空間的申請釋放工作由程式設計師控制,容易產生記憶體洩露
  • 空間的記憶體大小不同
    棧是由高地址向低地址擴充套件的資料結構,是一塊連續的記憶體區域。這句話的意思是棧頂的地址和最大容量是系統預先定好的,當申請的空間超過棧的剩餘空間的時,將會提示棧溢位。因此使用者能從棧獲得的空間較小。
    堆是由低地址向高地址擴充套件的一種資料結構,是不連續的記憶體區域。因為作業系統是用連結串列來儲存空閒地址的且連結串列的遍歷方式是由低地址向高地址。由此可見,堆獲得空間比較靈活也較大。
  • 是否產生碎片
    對於堆來講,頻繁的malloc/free勢必會造成記憶體空間的不連續,從而產生大量的碎片,使程式效率降低對於棧來講,不會出現這種問題。
  • 增長方向不同
    堆是從下向上增長的,即低地址向高地址增長。而棧是從上向下增長,即高地址向低地址。
  • 分配方式不同
  • 堆只能進行動態記憶體分配,通過malloc/free函式;但是棧既可以進行靜態記憶體分配也能進行動態記憶體分配,靜態記憶體分配是編譯器進行自動分配和釋放的,它利用alloca函式進行棧記憶體分配,但是不需要手動釋放,編譯器自動釋放。
  • 分配效率不同
    棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行。堆則是C函式庫提供的,它的機制很複雜,例如為了分配一塊記憶體,庫函式會按照一定的演算法(具體的演算法可以參考資料結構/作業系統)在堆記憶體中搜索可用的足夠大的空間,如果沒有足夠大的空間(可能是由於記憶體碎片太多),就有需要作業系統來重新整理記憶體空間,這樣就有機會分到足夠大小的記憶體,然後返回。顯然,堆的效率比棧要低得多。

記憶體分配方式

記憶體分配包括靜態分配和動態分配

  • 靜態分配:靜態分配是由編譯器完成的,比如自動變數的分配。棧具有靜態分配記憶體的方式,比如自動變數的分配。在程式執行之前完成,效率比較高。
    動態分配:動態分配是在程式執行的過程中動態的分配或者回收儲存空間的記憶體分配方法。其中堆總是動態分配記憶體的,而棧也可以進行動態記憶體的分配(使用alloca函式,不提倡這麼做)。在程式執行的過程中發生。

參考資料:《深入理解C指標》Richard Reese
部落格:C語言中的記憶體分配