眾所周知,c語言程式設計中,記憶體的分配和管理完全交由程式設計師來控制,因此,記憶體管理是每個c程式設計師必須熟練掌握的。
一般而言,分配給程序的記憶體有四個概念上不同的區域,分別為:**段、資料段、堆和棧,其中資料段又可以細分為初始化為非零的資料和初始化為零的資料。如下圖所示:
-------------------
| 程式棧 |----------高位址--〉低位址
-------------------
| 堆 |----------向上增長
-------------------
| bss |----------資料段
| 全域性和靜態變數 |
-----------------------------低位址
| 可執行** |----------**段
-------------------
可執行指令放在**段中,任何時刻,記憶體中只有乙份相同程式的指令拷貝,多個例項共享這些**。
初始化為非零的靜態資料和全域性資料存放在資料段中,執行相同程式的每個程序,有自己的資料段。
初始化為零的全域性資料和靜態分配資料存放在程序的bss區域中,每個執行的程序都有自己的bss,程式執行的時候,將資料放到資料段中,由此可知,只有初 始化為非零的變數才占用空間,所以對於類似static int ss[1024];這樣的陣列自動用0來填充,它佔的空間很小。
棧,分配本地變數的地方,函式引數、函式的返回值和返回位址也放在棧空間中,需要特別注意的是,當函式返回後,儲存在棧空間中的函式變數「自動消失」,空間被其他函式使用。棧空間是向下增長的。
在c語言中,一般通過malloc/calloc函式分配空間,通過free()函式釋放空間,使用realloc()改變已分配空間的大小。
分配記憶體的步驟:
1.申明乙個指定型別的指標
2.計算要分配空間的大小,一般使用函式sizeof()來實現
3.呼叫函式malloc()完成空間的申請,將函式的返回值賦給指標變數,
4.檢查返回值是否不為null,保證空間分配成功
5.分配好的空間是沒有經過初始化的,其中可能包含一些垃圾資訊,因此
呼叫函式memset()將其用0來填充是個好的習慣
釋放記憶體步驟:
1.呼叫函式free()釋放掉空間
注意:1.不可以使用free()掉後的空間
2.free()後,最好將指標置為null,因為如果不做這步處理,
原來的指標依舊指向剛才釋放的空間,可以繼續操作
3.避免重複釋放空間
在unix系統上,提供了函式alloca()函式,可以實現在棧空間上分配指定大小的空間,這樣的好處是,函式結束後,空間自動釋放,不必顯式地呼叫函式free(),但是該函式有很多弊端,比如不可移植等,因此不建議使用。
有必要提一下malloc、calloc、realloc函式的底層實現,在linux系統中,提供了brk()和sbrk()函式,上面幾個函式就是在這兩個函式的基礎上實現的。
程式設計師們經常編寫記憶體管理程式,往往提心吊膽。如果不想觸雷,唯一的解決辦法就是發現所有潛伏的地雷並且排除它們,躲是躲不了的。本章的內容比一般教科書的要深入得多,讀者需細心閱讀,做到真正地通曉記憶體管理。
7.1記憶體分配方式
記憶體分配方式有三種:
(1) 從靜態儲存區域分配。內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在。例如全域性變數,static變數。
(2) 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
(3) 從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
7.2常見的記憶體錯誤及其對策
發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時使用者怒氣沖沖地把你找來,程式卻沒有發生任何問題,你一走,錯誤又發作了。
常見的記憶體錯誤及其對策如下:
u 記憶體分配未成功,卻使用了它。
程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,在使用記憶體之前檢查指標是否為null。如果指標p是函式的引數,那麼在函式的入口處用assert(p!=null)進行檢查。如果是用malloc或new來申請記憶體,應該用if(p==null) 或if(p!=null)進行防錯處理。
u 記憶體分配雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。
記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
u 記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。
例如在使用陣列時經常發生下標「多1」或者「少1」的操作。特別是在for迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。
u 忘記了釋放記憶體,造成記憶體洩露。
動態記憶體的申請與釋放必須配對,程式中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。
u 釋放了記憶體卻繼續使用它。
有三種情況:
(1)程式中的物件呼叫關係過於複雜,實在難以搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。
(2)函式的return語句寫錯了,注意不要返回指向「棧記憶體」的「指標」或者「引用」,因為該內存在函式體結束時被自動銷毀。
(3)使用free或delete釋放了記憶體後,沒有將指標設定為null。導致產生「野指標」。
l 【規則7-2-1】用malloc或new申請記憶體之後,應該立即檢查指標值是否為null。防止使用指標值為null的記憶體。
l 【規則7-2-2】不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。
l 【規則7-2-3】避免陣列或指標的下標越界,特別要當心發生「多1」或者「少1」操作。
l 【規則7-2-4】動態記憶體的申請與釋放必須配對,防止記憶體洩漏。
l 【規則7-2-5】用free或delete釋放了記憶體之後,立即將指標設定為null,防止產生「野指標」。
C語言深入學習一
之前學過c語言不過比較淺顯,好多功能都不知道為什麼是這麼得到的,機緣巧合之下,找到了關於c語言深入學習的資源。很是開心。今天就分享給大家。希望大家能有所收穫。c語言中的資料型別 主要分為整形,浮點型,字元型。其對應為int等,float等,char等。typedef 是給某種功能另外叫乙個名字。可以...
C語言深入學習之指標
一 指標是什麼?c語言裡,變數都是存放在記憶體裡,而記憶體其實就是一組有序位元組組成的陣列,每個位元組都有唯一的記憶體位址。cpu 通過記憶體定址對儲存在記憶體中的某個指定資料物件的位址進行定位。這裡,資料物件是指儲存在記憶體中的乙個指定資料型別的數值或字串,它們都有乙個自己的位址,而指標便是儲存這...
C語言 陣列的深入學習
深入學習一下陣列 單元是依次相連線的。而且這多個變數必須單獨訪問,不可以一起訪問的。因為他們的位址彼此相連,所以使用指標 訪問這些變數就很容易了。這個位址在編譯器中決定具體數值,具體數值和變數名繫結,變數型別決定了這個位址的延續長度。3 陣列中幾個關鍵符號 a a 0 a a 0 的理解 1 a就是...