C/C++內存分配

1、      預備知識—程序的內存分配:ios

一個由C/C++編譯的程序佔用的內存分爲如下幾個部分:
一、棧區(stack)—由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操做方式相似於數據結構中的棧。
二、堆區(heap)—通常由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。注意它與數據結構中的堆是兩回事,分配方式卻是相似於鏈表。
三、全局區(靜態區)(static)—全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。程序結束後由系統釋放。
四、文字常量區—常量字符串就是放在這裏的。程序結束後由系統釋放。
五、程序代碼區程序員

舉例來講:編程

 1 //main.cpp
 2 int a=0;    //全局初始化區
 3 char *p1;   //全局未初始化區
 4 main()
 5 {
 6    int b;//
 7    char s[]="abc";  //
 8    char *p2;        //
 9    char *p3="123456";   //123456\0在常量區,p3在棧上。
10    static int c=0//全局(靜態)初始化區
11    p1 = (char*)malloc(10);
12    p2 = (char*)malloc(20);   //分配得來得10和20字節的區域就在堆區。
13    strcpy(p1,"123456");   //123456\0放在常量區,編譯器可能會將它與p3所向"123456"優化成一個地方。
14 }


2、堆和棧的理論知識數組

2.1申請方式
stack(棧):由系統自動分配。例如,聲明在函數中一個局部變量int b;系統自動在棧中爲b開闢空間
heap(堆):須要程序員本身申請,並指明大小,在c中malloc函數如p1=(char*)malloc(10);;如p2=(char*)malloc(10);可是注意p一、p2自己是在棧中的。在C++中用new運算符。
2.2申請後系統的響應
棧:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
堆:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序,另外,對於大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
2.3申請大小的限制
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
2.4申請效率的比較:
棧:由系統自動分配,速度較快。但程序員是沒法控制的。
堆:是由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便。數據結構

另外,在WINDOWS下,最好的方式是用Virtual Alloc分配內存,他不是在堆,也不是在棧,而是直接在進程的地址空間中保留一塊內存,雖然用起來最不方便。可是速度快,也最靈活。VirtualAlloc是Windows提供的API,一般用來分配大塊的內存。例如若是想在進程A和進程B之間經過共享內存的方式實現通訊,可使用該函數(這也是較經常使用的狀況)。函數

2.5堆和棧中的存儲內容
棧:在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變優化

量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
堆:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容由程序員安排。
2.6存取效率的比較
char s1[]="aaaaaaaaaaaaaaa";
char *s2="bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在運行時刻賦值的;
而bbbbbbbbbbb是在編譯時就肯定的;
可是,在之後的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。
好比:spa

 1 #include <iostream>
 2 void main()
 3 {
 4 char a=1;
 5 char c[]="1234567890";
 6 char *p="1234567890";
 7 a = c[1];
 8 a = p[1];
 9 return;
10 }


對應的彙編代碼
10:a=c[1];
004010678A4DF1movcl,byteptr[ebp-0Fh]
0040106A884DFCmovbyteptr[ebp-4],cl
11:a=p[1];
0040106D8B55ECmovedx,dwordptr[ebp-14h]
004010708A4201moval,byteptr[edx+1]
004010738845FCmovbyteptr[ebp-4],al
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據操作系統

edx讀取字符,顯然慢了。
2.7小結:
堆和棧的區別能夠用以下的比喻來看出:
使用棧就象咱們去飯館裏吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,沒必要理會切菜、洗菜等準備工做和洗碗、刷鍋等掃尾工做,他的好處是快捷,可是自由度小。
使用堆就象是本身動手作喜歡吃的菜餚,比較麻煩,可是比較符合本身的口味,並且自由度大。線程

3、區分char *和char []

char *定義的是一個指向字符串的指針(注意:C語言中沒有對應字符串的內置類型或者類類型),而char []就是C語言中的用來定義字符數組(注意:字符數組是不一樣於字符串,若是字符數組以'/0'結尾,那麼能夠視爲字符串)。

char a[]在運行時賦值,值會從靜態區賦值到函數的棧中,對它進行修改不會產生任何問題。char *a在編譯時就肯定了,a指向靜態區中的值,沒有賦值到函數棧中, 所以對指針的內容進行修改會產生錯誤。

問題引入:

一個的錯誤,一樣char *c = "abc"和char c[]="abc",前者改變其內

容程序是會崩潰的,然後者徹底正確。
程序演示:

#include <iostream>
using namespace std;

main()
{
   char *c1 = "abc";
   char c2[] = "abc";
   char *c3 = ( char* )malloc(3);
   c3 = "abc";
   printf("%d %d %s\n",&c1,c1,c1);
   printf("%d %d %s\n",&c2,c2,c2);
   printf("%d %d %s\n",&c3,c3,c3);
   getchar();
}   


運行結果
2293628 4199056 abc
2293624 2293624 abc
2293620 4199056 abc

自我總結:
char *c1 = "abc";實際上先是在文字常量區分配了一塊內存放"abc",而後在棧上分配一地址給c1並指向這塊地址,而後改變常量"abc"天然會崩潰

然而char c2[] = "abc",實際上abc分配內存的地方和上者並不同,能夠從
4199056
2293624 看出,徹底是兩塊地方,推斷4199056處於常量區,而2293624處於棧區

2293628
2293624
2293620 這段輸出看出三個指針分配的區域爲棧區,並且是從高地址到低地址

2293620 4199056 abc 看出編譯器將c3優化指向常量區的"abc"


繼續思考:
代碼:

#include <iostream>
using namespace std;

main()
{
   char *c1 = "abc";
   char c2[] = "abc";
   char *c3 = ( char* )malloc(3);
   // *c3 = "abc" //error
   strcpy(c3,"abc");
   c3[0] = 'g';
   printf("%d %d %s\n",&c1,c1,c1);
   printf("%d %d %s\n",&c2,c2,c2);
   printf("%d %d %s\n",&c3,c3,c3);
   getchar();
}   


輸出:
2293628 4199056 abc
2293624 2293624 abc
2293620 4012976 gbc
寫成註釋那樣,後面改動就會崩潰
可見strcpy(c3,"abc");abc是另外一塊地方分配的,並且能夠改變。

C3指針自己在棧上面,但指向內容已經在堆上。

4、Windows中不一樣的內存分配方式

(1) malloc

C語言的內存分配函數,用於分配通常的內存空間,該函數分配的內存不會自動進行初始化。若是使用C語言編程,使用該函數。在Visual C++ 中,malloc函數會調用HeapAlloc函數。

malloc分配的內存由free函數釋放。

(2) new

C++語言的實現方式,在Visual C++ 中,經過調用HeapAlloc實現內存分配,若是使用C++編程,建議使用new進行通常內存的分配。系統根據調用的方式決定是否對對象進行初始化。

注意: new 在C++中其實是操做符而不是函數。

使用new 分配的內存由delete / delete[] 進行釋放。

(3) VirtualAlloc

PVOID VirtualAlloc(PVOID pvAddress, SIZE_T dwSize, DWORD fdwAllocationType, DWORD fdwProtect)

VirtualAlloc是Windows提供的API,一般用來分配大塊的內存。例如若是想在進程A和進程B之間經過共享內存的方式實現通訊,可使用該函數(這也是較經常使用的狀況)。不要用該函數實現一般狀況的內存分配。該函數的一個重要特性是能夠預約指定地址和大小的虛擬內存空間。例如,但願在進程的地址空間中第50MB的地方分配內存,那麼將參數 50*1024*`1024 = 52428800 傳遞給pvAddress,將須要的內存大小傳遞給dwSize。若是系統有足夠大的閒置區域能知足請求,則系統會將該塊區域預訂下來並返回預訂內存的基地址,不然返回NULL。

使用VirtualAlloc分配的內存須要使用VirtualFree來釋放。

(4) HeapAlloc

HeapAlloc是Windows提供的API,在進程初始化的時候,系統會在進程的地址空間中建立1M大小的堆,稱爲默認堆(Default Heap),該大小爲默認值,能夠經過/HEAP鏈接器開關進行修改。用戶也能夠經過HeapCreate建立額外的堆,堆的使用能夠更有效的進行內存管理,避免線程同步的開銷以及快速的釋放內存等。HeapAlloc用於從堆上分配一個內存塊,若是分配成功則返回內存塊的地址。HeapAlloc內部會根據請求的大小以及堆的大小來決定具體的實現,例如在須要大的內存空間時,會自動調用VirtualAlloc函數分配空間。該函數一般用來分配通常大小的內存空間,一些Windows API可能會要求使用該函數進行內存分配並傳遞給API參數。注意,在分配大的內存塊時(例如1M或者更多)最好避免使用堆函數,建議使用VirtualAlloc。

使用HeapFree釋放由HeapAlloc的分配的內存。

相關文章
相關標籤/搜索