C 動態記憶體管理

2021-08-19 17:56:09 字數 4895 閱讀 9180

談到c語言動態記憶體管理,我們先要知道什麼是記憶體,它的大概布局是什麼樣子。

bss段:bss段(bss segment)通常是指用來存放程式中未初始化的全域性變數和靜態變數的一塊記憶體區域。bss是英文block started by symbol的簡稱。bss段屬於靜態記憶體分配。 bss節不包含任何資料,只是簡單的維護開始和結束的位址,即總大小,以便記憶體區能在執行時分配並被有效地清零。bss節在應用程式的二進位制映象檔案中並不存在,即不占用磁碟空間而只在執行的時候占用記憶體空間,所以如果全域性變數和靜態變數未初始化那麼其可執行檔案要小很多。

這裡注意乙個問題:一般的書上都會說全域性變數和靜態變數是會自動初始化的,那麼哪來的未初始化的變數呢?變數的初始化可以分為顯示初始化和隱式初始化,全域性變數和靜態變數如果程式設計師自己不初始化的話的確也會被初始化,那就是不管什麼型別都初始化為0,這種沒有顯示初始化的就是我們這裡所說的未初始化。既然都是0那麼就沒必要把每個0都儲存起來,從而節省磁碟空間,這是bss的主要作用。

資料段:資料段(data segment)通常是指用來存放程式中已初始化的全域性變數和靜態變數的一塊記憶體區域。資料段屬於靜態記憶體分配,可以分為唯讀資料段和讀寫資料段。 字串常量等,但一般都是放在唯讀資料段中。

**段:**段(code segment/text segment)通常是指用來存放程式執行**的一塊記憶體區域。這部分區域的大小在程式執行前就已經確定,並且記憶體區域通常屬於唯讀, 某些架構也允許**段為可寫,即允許修改程式。在**段中,也有可能包含一些唯讀的常數變數,例如字串常量等,但一般都是放在唯讀資料段中。

堆(heap):堆是用於存放程序執行中被動態分配的記憶體段,它的大小並不固定,可動態擴張或縮減。當程序呼叫malloc等函式分配記憶體時,新分配的記憶體就被動態新增到堆上(堆被擴張);當利用free等函式釋放記憶體時,被釋放的記憶體從堆中被剔除(堆被縮減) 

棧(stack):棧又稱堆疊, 是使用者存放程式臨時建立的區域性變數,也就是說我們函式括弧「{}」中定義的變數(但不包括static宣告的變數,static意味著在資料段中存放變數)。除此以外,在函式被呼叫時,其引數也會被壓入發起呼叫的程序棧中,並且待到呼叫結束後,函式的返回值也會被存放回棧中。由於棧的先進先出特點,所以棧特別方便用來儲存/恢復呼叫現場。從這個意義上講,我們可以把堆疊看成乙個寄存、交換臨時資料的記憶體區。注意:棧空間是向下增長的,每個執行緒有乙個自己的棧,在linux上預設的大小是8m,可以用ulimit檢視和修改。棧系統提供的功能,特點是快速高效,缺點是有限制,資料不靈活;而堆是函式庫提供的功能,特點是靈活方便,資料適應面廣泛,但是效率有一定降低

首先了解一下三者的函式原型:

void *malloc(size_t size);

malloc()在記憶體的動態儲存區中分配一塊長度為size位元組的連續區域。引數size為需要的記憶體空間的長度,返回該區域的位址。並且沒有初始化記憶體的內容,因此基本上malloc之後,呼叫函式memset來初始化這部分的記憶體空間。

如果由malloc()函式分配的記憶體空間原來沒有被使用過,則其中的每一位可能都是0;反之, 如果這部分記憶體曾經被分配過,則其中可能遺留有各種各樣的資料.也就是說,使用malloc()函式的程式開始時(記憶體空間還沒有被重新分配)能正常進行,但經過一段時間(記憶體空間還已經被重新分配)可能會出現問題。

void *calloc(size_t nmemb, size_t size);

calloc()與malloc()相似,引數size為申請位址的單位元素長度,nmemb為引數個數。將初始化這部分的記憶體,設定為0.如果你是為字元型別或整數型別的元素分配記憶體,那麼這些元素將保證會被初始化為0;如果你是為指標型別的元素分配記憶體,那麼這些元素通常會被初始化為空指標;如果你為實型資料分配記憶體,則這些元素會被初始化為浮點型的零。

void *realloc(void *ptr, size_t size);

realloc()是給乙個已經分配了位址的指標重新分配空間,引數ptr為原有的空間位址,newsize是重新申請的位址空間。realloc可以對給定的指標所指向的空間進行擴大或縮小,原有的記憶體中的內容將保持不變。realloc並不保持調整後的記憶體空間和原來的記憶體空間保持同一記憶體位址,返回的指標很可能指向新的位址。

realloc是從堆上分配記憶體的.當擴大一塊記憶體空間時,realloc()試圖直接從堆上現存的資料後面的那些位元組中獲得附加的位元組,如果能夠滿足,自然天下太平;如果資料後面的位元組不夠,問題就出來了,那麼就使用堆上第乙個有足夠大小的自由塊,現存的資料然後就被拷貝至新的位置,而老塊則放回到堆上.這句話傳遞的乙個重要的資訊就是資料可能被移動.

堆上的記憶體需要使用者自己來管理,開闢空間後記得用free()來釋放空間,否則會發生記憶體洩漏。

void * __cdecl _alloca(size_t);

由於是在棧上開闢記憶體,而棧上開闢的記憶體由編譯器自動維護,不需要使用者的顯示釋放,所以就存在了一些問題。如果作為返回值傳給另外的函式時,由於函式呼叫會及時的釋放棧上的空間,所以在作為返回值傳入時,空間早已釋放,就會出現錯誤。

我們知道c++中是可以使用c庫中的函式的,但是c++中又新增了很多c語言中沒有的,比如申請記憶體,通常在c++中申請記憶體用new,與之相應配對使用的是delete來釋放記憶體,兩者並不是c庫的函式,而是c++的運算子,那麼問題來了,為什麼可以用c庫函式來申請記憶體還需要new和delete運算子呢??

原因在於在c++中申請物件空間時,往往會呼叫建構函式和析構函式,這是編譯器自動呼叫的,而malloc是c庫函式,不在編譯器的控制許可權內,所以不能強行把呼叫建構函式和析構函式的任務分配給它,所以才需要new和delete運算子,並且malloc和free配對使用,兩者不能混搭操作。

c++中可以使用c庫函式來開闢堆上的空間,也可以使用new和delete來開闢空間。簡單介紹一下使用方法

int  *p1=new int;   它表示開闢了乙個整形大小的空間

int  *p2=new int(3);    它表示開闢了1個大小的整形空間,並且賦值為3

int  *p3=new int[3];    它表示開闢了3個大小的整形空間

同時配對使用它們的釋放運算子

delete(p1);

delete(p2);

deletep3;

千萬要注意各個運算子的配對使用,否則會發生錯誤的。

new-->operator new(使用new運算子時,其實會跳轉到operator new函式)--->在函式體內再利用malloc申請空間----->再呼叫建構函式--->返回開闢的首位址。

delete-->operator delete(使用delete運算子時,其實會跳轉到operator delete函式)----->再呼叫析構函式--->在函式體內再利用free釋放空間。

new[count]-->operator new[count](使用new運算子時,其實會跳轉到operator new[count]函式)--->在函式體內再利用malloc申請空間----->再呼叫建構函式(call count次)--->返回開闢的首位址。

delete-->operator delete(使用delete運算子時,其實會跳轉到operator delete函式)----->再呼叫析構函式(call count次)--->在函式體內再利用free釋放空間。

1. new/delete和malloc/free的區別

(1)malloc與free是c++/c語言的標準庫函式。new/delete是c++的運算子。它們都可以申請動態記憶體和釋放記憶體。

(2)對於非內部資料型別的物件而言,用malloc/free無法滿足動態物件的要求(物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行析構函式)。

(3)由於malloc/free是庫函式而不是運算子,不在編譯器控制許可權之內,不能夠把執行建構函式和析構函式的任務強加於malloc/free。因此,c++語言需要可以完成動態記憶體分配和初始化工作的運算子new,以及乙個能完成清理與釋放記憶體工作的運算子delete。

(4)都是在堆(heap)上進行動態的記憶體操作。用malloc函式需要指定記憶體分配的位元組數並且不能初始化物件。new會自動呼叫物件的建構函式。

delete 會呼叫物件的destructor,而free 不會呼叫物件的destructor。

2.new/delete和new/delete的區別

new[count]則需要比new多開闢4個位元組來存放count.

通常我們所說的記憶體洩漏,是指分配出去的內存在使用之後沒有釋放掉,沒有**,長此以往,會造成沒有足夠的記憶體可以分配。一般表現為執行時間越長,占用的記憶體越多,最終導致系統奔潰。一般的記憶體洩漏是指堆記憶體的洩漏。堆記憶體是指程式從堆中分配的,大小任意的(記憶體塊的大小可以在程式執行期決定),使用完後必須顯式釋放的記憶體。應用程式一般使用malloc,realloc,new等函式從堆中分配到一塊記憶體,使用完後,程式必須負責相應的呼叫free或delete釋放該記憶體塊,否則,這塊記憶體就不能被再次使用,我們就說這塊記憶體洩漏了。

有乙個很簡單的辦法來檢查乙個程式是否有記憶體洩漏。就是是用windows的任務管理器(task manager)。執行程式,然後在任務管理器裡面檢視 「記憶體使用」和」虛擬記憶體大小」兩項,當程式請求了它所需要的記憶體之後,如果虛擬記憶體還是持續的增長的話,就說明了這個程式有記憶體洩漏問題。 當然如果記憶體洩漏的數目非常的小,用這種方法可能要過很長時間才能看的出來。 

當然最簡單的辦法大概就是用compuware的boundchecker 之類的工具來檢測了,不過這些工具的**對於個人來講稍微有點奢侈了。 

如果是已經發布的程式,檢查是否有記憶體洩漏是又費時又費力。所以記憶體洩漏應該在code的生成過程就要時刻進行檢查。 

C 動態記憶體管理

我們都知道在c 中可以用new malloc動態分配記憶體空間,delete free釋放動態開闢的記憶體空間。1.那麼既然c 中有了可以動態開闢記憶體的函式為什麼又要有new delete呢?c 中的malloc free是繼承c語言中的malloc free,它的用法和在c語言中的用法一模一樣。...

C 動態記憶體管理

1 總結並剖析malloc free和new delete之間關係和差異。1 他們都是動態記憶體管理的入口 2 malloc要計算空間大小,返回值要強轉 new自動計算位元組大小,返回值是相應型別的指標 3 malloc只開闢空間 new開闢空間 呼叫建構函式初始化 delete呼叫析構函式清理 釋...

c 動態記憶體管理

c語言動態記憶體管理 c中關於動態記憶體的標準庫函式 malloc calloc realloc free 以下是關於這幾個函式的介紹 1 malloc 用於動態開闢記憶體 堆空間 返回型別為void 引數 size t size 是無符號整型表示要開闢的空間大小,單位是位元組,2 calloc 用...