1. 程式人生 > >c++ 堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區

c++ 堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區

char *s1 = "ni";
char *s2 = "";

int _tmain(int argc, _TCHAR* argv[])
{
	//在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。

	//棧空間
	int a=0;
	int b=0;
	printf("棧空間\n");
	printf("%p\n",&argc);
	printf("%p\n",&a);
	printf("%p\n",&b);
	printf("---------------------------\n");
	//全域性與靜態空間
	printf("全域性與靜態空間\n");
	printf("%p\n",&s1);
	printf("%p\n",&s2);
	printf("---------------------------\n");
	//自由儲存區
	printf("自由儲存區\n");
	char **r;
	char *p0 =(char *) malloc(4); //p0指向堆
	r = &p0;
	printf("%p\n",*r);
	printf("---------------------------\n");
	//堆空間
	printf("堆空間\n");
	char *newp = new char;
	r = &newp;
	printf("%p\n",*r);
	printf("---------------------------\n");
	//常量儲存區
	printf("常量儲存區\n");
	p0 = "ni"; //p0不再指向堆,而是常量區
	r = &p0;
	printf("%p\n",*r);
	
	printf("********************************************\n");

	p0 =(char *) malloc(4); //p0重新指向自由儲存區
	r = &p0;
	printf("%p\n",*r);
	char *str = "ni";
	int i = 0;
	for(i=0;i<3;i++)
		p0[i] = str[i];
	r = &p0;
	printf("%p\n",*r);//p0依然指向自由儲存區的同一個地址,所以動態串要通過這種方式來去改變串的內容
					  //不然在使用realloc時會報錯
	printf("---------------------------\n");
	free(p0);
	return 0;
}

 1.:在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
 2.:就是那些由 new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new就要對應一個 delete。如果程式設計師沒有釋放掉,那麼在程式結束後,作業系統會自動回收。
 3.自由儲存區:就是那些由malloc等分配的記憶體塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
 4.全域性/靜態儲存區:全域性變數和靜態變數被分配到同一塊記憶體中,在以前的C語言中,全域性變數又分為初始化的和未初始化的,在C++裡面沒有這個區分了,他們共同佔用同一塊記憶體區。

 5.常量儲存區:這是一塊比較特殊的儲存區,他們裡面存放的是常量,不允許修改。

明確區分堆與棧

  堆與棧的區分問題,似乎是一個永恆的話題,由此可見,初學者對此往往是混淆不清的,所以我決定拿他第一個開刀。
  首先,我們舉一個例子:

1 void f() { int* p=new int[5]; }

  這條短短的一句話就包含了堆與棧,看到new,我們首先就應該想到,我們分配了一塊堆記憶體,那麼指標p呢?他分配的是一塊棧記憶體,所以這句話的意思就是:在棧記憶體中存放了一個指向一塊堆記憶體的指標p。在程式會先確定在堆中分配記憶體的大小,然後呼叫operator new分配記憶體,然後返回這塊記憶體的首地址,放入棧中,他在VC6下的彙編程式碼如下:

00401028 push 14h
0040102A call operator new (00401060)
0040102F add esp,4
00401032 mov dword ptr [ebp-8],eax
00401035 mov eax,dword ptr [ebp-8]
00401038 mov dword ptr [ebp-4],eax

  這裡,我們為了簡單並沒有釋放記憶體,那麼該怎麼去釋放呢?是delete p麼?澳,錯了,應該是delete []p,這是為了告訴編譯器:我刪除的是一個數組,編譯器就會根據相應的Cookie資訊去進行釋放記憶體的工作。

堆和棧究竟有什麼區別

  好了,我們回到我們的主題:堆和棧究竟有什麼區別?
  主要的區別由以下幾點:
  (1). 管理方式不同
  (2). 空間大小不同
  (3). 能否產生碎片不同
  (4). 生長方向不同
  (5). 分配方式不同
  (6). 分配效率不同
  管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式設計師控制,容易產生memory leak
  空間大小:一般來講在32位系統下,堆記憶體可以達到4G的空間,從這個角度來看堆記憶體幾乎是沒有什麼限制的。但是對於棧來講,一般都是有一定的空間大小的,例如,在VC6下面,預設的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改:
  開啟工程,依次操作選單如下:Project->Setting->Link,在Category 中選中Output,然後在Reserve中設定堆疊的最大值和commit
  注意:reserve最小值為4Byte;commit是保留在虛擬記憶體的頁檔案裡面,它設定的較大會使棧開闢較大的值,可能增加記憶體的開銷和啟動時間。
  碎片問題:對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個問題,因為棧是先進後出的佇列,他們是如此的一一對應,以至於永遠都不可能有一個記憶體塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出,詳細的可以參考資料結構,這裡我們就不再一一討論了。
  生長方向:對於堆來講,生長方向是向上的,也就是向著記憶體地址增加的方向;對於棧來講,它的生長方向是向下的,是向著記憶體地址減小的方向增長。
  分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由alloca函式進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
  分配效率:棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函式庫提供的,它的機制是很複雜的,例如為了分配一塊記憶體,庫函式會按照一定的演算法(具體的演算法可以參考資料結構/作業系統)在堆記憶體中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於記憶體碎片太多),就有可能呼叫系統功能去增加程式資料段的記憶體空間,這樣就有機會分到足夠大小的記憶體,然後進行返回。顯然,堆的效率比棧要低得多。
  從這裡我們可以看到,堆和棧相比,由於大量new/delete的使用,容易造成大量的記憶體碎片;由於沒有專門的系統支援,效率很低;由於可能引發使用者態和核心態的切換,記憶體的申請,代價變得更加昂貴。所以棧在程式中是應用最廣泛的,就算是函式的呼叫也利用棧去完成,函式呼叫過程中的引數,返回地址,EBP和區域性變數都採用棧的方式存放。所以,我們推薦大家儘量用棧,而不是用堆。
  雖然棧有如此眾多的好處,但是由於和堆相比不是那麼靈活,有時候分配大量的記憶體空間,還是用堆好一些。
  無論是堆還是棧,都要防止越界現象的發生(除非你是故意使其越界),因為越界的結果要麼是程式崩潰,要麼是摧毀程式的堆、棧結構,產生以想不到的結果,就算是在你的程式執行過程中,沒有發生上面的問題,你還是要小心,說不定什麼時候就崩掉,那時候debug可是相當困難的:)

控制C++的記憶體分配

  在嵌入式系統中使用C++的一個常見問題是記憶體分配,即對new 和 delete 操作符的失控。
  具有諷刺意味的是,問題的根源卻是C++對記憶體的管理非常的容易而且安全。具體地說,當一個物件被消除時,它的解構函式能夠安全的釋放所分配的記憶體。
  這當然是個好事情,但是這種使用的簡單性使得程式設計師們過度使用new 和 delete,而不注意在嵌入式C++環境中的因果關係。並且,在嵌入式系統中,由於記憶體的限制,頻繁的動態分配不定大小的記憶體會引起很大的問題以及堆破碎的風險。
  作為忠告,保守的使用記憶體分配是嵌入式環境中的第一原則。
  但當你必須要使用newdelete時,你不得不控制C++中的記憶體分配。你需要用一個全域性的new 和delete來代替系統的記憶體分配符,並且一個類一個類的過載newdelete
  一個防止堆破碎的通用方法是從不同固定大小的記憶體持中分配不同型別的物件。對每個類過載new 和delete就提供了這樣的控制。

過載全域性的new和delete操作符

  可以很容易地過載new 和 delete 操作符,如下所示:

1 2 3 4 5 6 7 8 void * operator new(size_t size){ void *p = malloc(size); return (p); } void operator delete(void *p){ free(p); }

  這段程式碼可以代替預設的操作符來滿足記憶體分配的請求。出於解釋C++的目的,我們也可以直接呼叫malloc()free()
  也可以對單個類的new 和 delete操作符過載。這是你能靈活的控制物件的記憶體分配。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class TestClass { public: void * operator new(size_t size); void operator delete(void *p); // .. other members here ... }; void *TestClass::operator new(size_t size){ void *p = malloc(size); // Replace this with alternative allocator return (p); } void TestClass::operator delete(void *p){ free(p); // Replace this with alternative de-allocator }

  所有TestClass 物件的記憶體分配都採用這段程式碼。更進一步,任何從TestClass 繼承的類也都採用這一方式,除非它自己也過載了new 和 delete 操作符。通過過載new 和 delete 操作符的方法,你可以自由地採用不同的分配策略,從不同的記憶體池中分配不同的類物件。

為單個的類過載new[]和delete[]

  必須小心物件陣列的分配。你可能希望呼叫到被你過載過的new 和 delete 操作符,但並不如此。記憶體的請求被定向到全域性的new[]delete[] 操作符,而這些記憶體來自於系統堆。
  C++將物件陣列的記憶體分配作為一個單獨的操作,而不同於單個物件的記憶體分配。為了改變這種方式,你同樣需要過載new[] 和 delete[]操作符。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class TestClass { public: void * operator new[ ](size_t size); void operator delete[ ](void *p); // .. other members here .. }; void *TestClass::operator new[ ](size_t size){ void *p = malloc(size); return (p); } void TestClass::operator delete[ ](void *p){ free(p); } int main(void){ TestClass *p = new TestClass[10]; // ... etc ... delete[ ] p; }

  但是注意:對於多數C++的實現,new[]操作符中的個數引數是陣列的大小加上額外的儲存物件數目的一些位元組。在你的記憶體分配機制重要考慮的這一點。你應該儘量避免分配物件陣列,從而使你的記憶體分配策略簡單。

常見的記憶體錯誤及其對策

  發生記憶體錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程式執行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時使用者怒氣衝衝地把你找來,程式卻沒有發生任何問題,你一走,錯誤又發作了。 常見的記憶體錯誤及其對策如下:

  • 記憶體分配未成功,卻使用了它。程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,在使用記憶體之前檢查指標是否為NULL。如果指標p是函式的引數,那麼在函式的入口處用assert(p!=NULL)進行檢查。如果是用mallocnew來申請記憶體,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。
  • 記憶體分配雖然成功,但是尚未初始化就引用它。犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
  • 記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。例如在使用陣列時經常發生下標“多1”或者“少1”的操作。特別是在for迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。
  • 忘記了釋放記憶體,造成記憶體洩露。含有這種錯誤的函式每被呼叫一次就丟失一塊記憶體。剛開始時系統的記憶體充足,你看不到錯誤。終有一次程式突然死掉,系統出現提示:記憶體耗盡。動態記憶體的申請與釋放必須配對,程式中mallocfree的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。
  • 釋放了記憶體卻繼續使用它。

  有三種情況:
  (1). 程式中的物件呼叫關係過於複雜,實在難以搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。
  (2). 函式的return語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函式體結束時被自動銷燬。
  (3). 使用freedelete釋放了記憶體後,沒有將指標設定為NULL導致產生“野指標”
  那麼如何避免產生野指標呢?這裡列出了5條規則,平常寫程式時多注意一下,養成良好的習慣。

規則1:用mallocnew申請記憶體之後,應該立即檢查指標值是否為NULL。防止使用指標值為NULL的記憶體。
規則2:不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。
規則3:避免陣列或指標的下標越界,特別要當心發生“多1”或者“少1”操作。
規則4:動態記憶體的申請與釋放必須配對,防止記憶體洩漏。
規則5:用freedelete釋放了記憶體之後,立即將指標設定為NULL,防止產生“野指標”。

針與陣列的對比

  C++/C程式中,指標和陣列在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。
  陣列要麼在靜態儲存區被建立(如全域性陣列),要麼在棧上被建立。陣列名對應著(而不是指向)一塊記憶體,其地址與容量在生命期內保持不變,只有陣列的內容可以改變。
  指標可以隨時指向任意型別的記憶體塊,它的特徵是“可變”,所以我們常用指標來操作動態記憶體。指標遠比陣列靈活,但也更危險。
  下面以字串為例比較指標與陣列的特性。

修改內容

  下面示例中,字元陣列a的容量是6個字元,其內容為 hello。a的內容可以改變,如a[0]= ‘X’。指標p指向常量字串“world”(位於靜態儲存區,內容為world),常量字串的內容是不可以被修改的。從語法上看,編譯器並不覺得語句p[0]= ‘X’有什麼不妥,但是該語句企圖修改常量字串的內容而導致執行錯誤。

1 2 3 4 5 6 char a[] = “hello”; a[0] = ‘X’; cout << a << endl; char *p = “world”; // 注意p指向常量字串 p[0] = ‘X’; // 編譯器不能發現該錯誤 cout << p << endl;

內容複製與比較

  不能對陣列名進行直接複製與比較。若想把陣列a的內容複製給陣列b,不能用語句 b = a ,否則將產生編譯錯誤。應該用標準庫函式strcpy進行復制。同理,比較b和a的內容是否相同,不能用if(b==a) 來判斷,應該用標準庫函式strcmp進行比較。
  語句 p = a 並不能把a的內容複製指標p,而是把a的地址賦給了p。要想複製a的內容,可以先用庫函式malloc為p申請一塊容量為strlen(a)+1個字元的記憶體,再用strcpy進行字串複製。同理,語句if(p==a) 比較的不是內容而是地址,應該用庫函式strcmp來比較。

1 2 3 4 5 6 7 8 9 10 11 12 13 // 陣列… char a[] = "hello"; char b[10]; strcpy(b, a); // 不能用 b = a; if(strcmp(b, a) == 0) // 不能用 if (b == a)// 指標… int len = strlen(a); char *p = (char *)malloc(sizeof(char)*(len+1)); strcpy(p,a); // 不要用 p = a; if(strcmp(p, a) == 0) // 不要用 if (p == a)

計算記憶體容量

  用運算子sizeof可以計算出陣列的容量(位元組數)。如下示例中,sizeof(a)的值是12(注意別忘了’’)。指標p指向a,但是sizeof(p)的值卻是4。這是因為sizeof(p)得到的是一個指標變數的位元組數,相當於sizeof(char*),而不是p所指的記憶體容量。C++/C語言沒有辦法知道指標所指的記憶體容量,除非在申請記憶體時記住它。

1 2 3 4 char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12位元組 cout<< sizeof(p) << endl; // 4位元組

  注意當陣列作為函式的引數進行傳遞時,該陣列自動退化為同類型的指標。如下示例中,不論陣列a的容量是多少,sizeof(a)始終等於sizeof(char *)

1 2 3 void Func(char a[100]){ cout<< sizeof(a) << endl; // 4位元組而不是100位元組 }

指標引數是如何傳遞記憶體的

  如果函式的引數是一個指標,不要指望用該指標去申請動態記憶體。如下示例中,Test函式的語句GetMemory(str, 200)並沒有使str獲得期望的記憶體,str依舊是NULL,為什麼?

1 2 3 4 5 6 7 8 9 10 void GetMemory(char *p, int num){ p = (char *)malloc(sizeof(char) * num); } void Test(void){ char *str = NULL; GetMemory(str, 100); // str 仍然為 NULL strcpy(str, "hello"); // 執行錯誤 }

  毛病出在函式GetMemory中。編譯器總是要為函式的每個引數製作臨時副本,指標引數p的副本是 _p,編譯器使_p=p。如果函式體內的程式修改了_p的內容,就導致引數p的內容作相應的修改。這就是指標可以用作輸出引數的原因。在本例中,_p申請了新的記憶體,只是把 _p所指的記憶體地址改變了,但是p絲毫未變。所以函式GetMemory並不能輸出任何東西。事實上,每執行一次GetMemory就會洩露一塊記憶體,因為沒有用free釋放記憶體。
  如果非得要用指標引數去申請記憶體,那麼應該改用“指向指標的指標”,見示例:

1 2 3 4 5 6 7 8 9 10 11 12 void GetMemory2(char **p, int num){ *p = (char *)malloc(sizeof(char) * num); } void Test2(void){ char *str = NULL; GetMemory2(&str, 100); // 注意引數是 &str,而不是str strcpy(str, "hello"); cout<< str << endl; free(str); }

  由於“指向指標的指標”這個概念不容易理解,我們可以用函式返回值來傳遞動態記憶體。這種方法更加簡單,見示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 char *GetMemory3(int num){ char *p = (char *)malloc(sizeof(char) * num); return p; } void Test3(void){ char *str = NULL; str = GetMemory3(100); strcpy(str, "hello"); cout<< str << endl; free(str); }

  用函式返回值來傳遞動態記憶體這種方法雖然好用,但是常常有人把return語句用錯了。這裡強調不要用return語句返回指向“棧記憶體”的指標,因為該記憶體在函式結束時自動消亡,見示例:

1 2 3 4 5 6 7 8 9 10 char *GetString(void){ char p[] = "hello world"; return p; // 編譯器將提出警告 } void Test4(void){ char *str = NULL; str = GetString(); // str 的內容是垃圾 cout<< str << endl; }

  用偵錯程式逐步跟蹤Test4,發現執行str = GetString語句後str不再是NULL指標,但是str的內容不是“hello world”而是垃圾。
  如果把上述示例改寫成如下示例,會怎麼樣?

1 2 3 4 5 6 7 8 9 10 char *GetString2(void){ char *p = "hello world"; return p; } void Test5(void){ char *str = NULL; str = GetString2(); cout<< str << endl; }

  函式Test5執行雖然不會出錯,但是函式GetString2的設計概念卻是錯誤的。因為GetString2內的“hello world”是常量字串,位於靜態儲存區,它在程式生命期內恆定不變。無論什麼時候呼叫GetString2,它返回的始終是同一個“只讀”的記憶體塊。

杜絕“野指標”

  “野指標”不是NULL指標,是指向“垃圾”記憶體的指標。人們一般不會錯用NULL指標,因為用if語句很容易判斷。但是“野指標”是很危險的,if語句對它不起作用。 “野指標”的成因主要有三種:

  (1). 指標變數沒有被初始化。任何指標變數剛被建立時不會自動成為NULL指標,它的預設值是隨機的,它會亂指一氣。所以,指標變數在建立的同時應當被初始化,要麼將指標設定為NULL,要麼讓它指向合法的記憶體。例如:

1 2 char *p = NULL; char *str = (char *) malloc(100);

  (2). 指標p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指標。

  (3). 指標操作超越了變數的作用域範圍。這種情況讓人防不勝防,示例程式如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 class A{ public: void Func(void){ cout << “Func of class A” << endl; } }; void Test(void){ A *p; { A a; p = &a; // 注意 a 的生命期 } p->Func(); // p是“野指標” }

  函式Test在執行語句p->Func()時,物件a已經消失,而p是指向a的,所以p就成了“野指標”。但奇怪的是我執行這個程式時居然沒有出錯,這可能與編譯器有關。

有了malloc/free為什麼還要new/delete

  mallocfree是C++/C語言的標準庫函式,new/delete是C++的運算子。它們都可用於申請動態記憶體和釋放記憶體。
  對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free
  因此C++語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以及一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。我們先看一看malloc/freenew/delete如何實現物件的動態記憶體管理,見示例:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Obj{ public :   Obj(void){ cout << “Initialization” << endl; }   ~Obj(void){ cout << “Destroy” << endl; }   void Initialize(void){ cout << “Initialization” << endl; }   void Destroy(void){ cout << “Destroy” << endl; } }; void UseMallocFree(void){ Obj *a = (obj *)malloc(sizeof(obj)); // 申請動態記憶體 a->Initialize(); // 初始化 //… a->Destroy(); // 清除工作 free(a); // 釋放記憶體 } void UseNewDelete(void){ Obj *a = new Obj; // 申請動態記憶體並且初始化 //… delete a; // 清除並且釋放記憶體 }

  類Obj的函式Initialize模擬了建構函式的功能,函式Destroy模擬了解構函式的功能。函式UseMallocFree中,由於malloc/free不能執行建構函式與解構函式,必須呼叫成員函式InitializeDestroy來完成初始化與清除工作。函式UseNewDelete則簡單得多。
  所以我們不要企圖用malloc/free來完成動態物件的記憶體管理,應該用new/delete。由於內部資料型別的“物件”沒有構造與析構的過程,對它們而言malloc/freenew/delete是等價的。
  既然new/delete的功能完全覆蓋了malloc/free,為什麼C++不把malloc/free淘汰出局呢?這是因為C++程式經常要呼叫C函式,而C程式只能用malloc/free管理動態記憶體。
  如果用free釋放“new建立的動態物件”,那麼該物件因無法執行解構函式而可能導致程式出錯。如果用delete釋放“malloc申請的動態記憶體”,結果也會導致程式出錯,但是該程式的可讀性很差。所以new/delete必須配對使用,malloc/free也一樣。

記憶體耗盡怎麼辦

  如果在申請動態記憶體時找不到足夠大的記憶體塊,mallocnew將返回NULL指標,宣告記憶體申請失敗。通常有三種方式處理“記憶體耗盡”問題。
  (1). 判斷指標是否為NULL,如果是則馬上用return語句終止本函式。例如:

1 2 3 4 5 6 void Func(void){ A *a = new A; if(a == NULL) return; … }

  (2). 判斷指標是否為NULL,如果是則馬上用exit(1)終止整個程式的執行。例如:

1 2 3 4 5 6 7 8 9 void Func(void){ A *a = new A; if(a == NULL){ cout << “Memory Exhausted” << endl; exit(1); } … }

  (3). 為newmalloc設定異常處理函式。例如Visual C++可以用_set_new_hander函式為new設定使用者自己定義的異常處理函式,也可以讓malloc享用與new相同的異常處理函式。詳細內容請參考C++使用手冊。
  上述 (1)、(2) 方式使用最普遍。如果一個函式內有多處需要申請動態記憶體,那麼方式 (1) 就顯得力不從心(釋放記憶體很麻煩),應該用方式 (2) 來處理。
  很多人不忍心用exit(1),問:“不編寫出錯處理程式,讓作業系統自己解決行不行?”
  不行。如果發生“記憶體耗盡”這樣的事情,一般說來應用程式已經無藥可救。如果不用exit(1) 把壞程式殺死,它可能會害死作業系統。道理如同:如果不把歹徒擊斃,歹徒在老死之前會犯下更多的罪。
  有一個很重要的現象要告訴大家。對於32位以上的應用程式而言,無論怎樣使用malloc與new,幾乎不可能導致“記憶體耗盡”。對於32位以上的應用程式,“記憶體耗盡”錯誤處理程式毫無用處。這下可把Unix和Windows程式設計師們樂壞了:反正錯誤處理程式不起作用,我就不寫了,省了很多麻煩。
  必須強調:不加錯誤處理將導致程式的質量很差,千萬不可因小失大。

1 2 3 4 5 6 7 8 9 void main(void){ float *p = NULL; while(TRUE){ p = new float[1000000]; cout << “eat memory” << endl; if(p==NULL) exit(1); } }

malloc/free的使用要點

  函式malloc的原型如下:

1 void * malloc(size_t size);

  用malloc申請一塊長度為length的整數型別的記憶體,程式如下:

1 int *p = (int *) malloc(sizeof(int) * length);

  我們應當把注意力集中在兩個要素上:“型別轉換”和“sizeof”。
  * malloc返回值的型別是void*,所以在呼叫malloc時要顯式地進行型別轉換,將void *轉換成所需要的指標型別。
  * malloc函式本身並不識別要申請的記憶體是什麼型別,它只關心記憶體的總位元組數。我們通常記不住intfloat等資料型別的變數的確切位元組數。例如int變數在16位系統下是2個位元組,在32位下是4個位元組;而float變數在16位系統下是4個位元組,在32位下也是4個位元組。最好用以下程式作一次測試:

1 2 3 4 5 6 7 8 cout << sizeof(char) << endl; cout << sizeof(int) << endl; cout << sizeof(unsigned int) << endl; cout << sizeof(long) << endl; cout << sizeof(unsigned long) << endl; cout << sizeof(float) << endl; cout << sizeof(double) << endl; cout << 

相關推薦

c++ 自由儲存區全域性/靜態儲存區常量儲存區

char *s1 = "ni"; char *s2 = ""; int _tmain(int argc, _TCHAR* argv[]) { //在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。 //棧空間 int

記憶體分配方式詳解(自由儲存區全域性/靜態儲存區常量儲存區)

原文地址:https://blog.csdn.net/u013007900/article/details/79338653 參考文章:http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html 一、資料結構中的棧和堆 雖

Linux_C++記憶體分配方式詳解——自由儲存區全域性/靜態儲存區常量儲存區

棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。在一個程序中,位於使用者虛擬地址空間頂部的是使用者棧,編譯器用它來實現函式的呼叫。和堆一樣,使用者棧在程式執行期間可以動態地擴充套件和收縮。   堆,就是那些

自由儲存區 全域性/靜態儲存區常量儲存區

在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。 棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。 堆,就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new

C#

完成 time bool 語言 收集器 一段 兩個 介紹 在一起 解釋1、棧是編譯期間就分配好的內存空間,因此你的代碼中必須就棧的大小有明確的定義;堆是程序運行期間動態分配的內存空間,你可以根據程序的運行情況確定要分配的堆內存的大小 解釋2、 存放在棧中時要管存儲順序,

[轉] c++ &

[轉自 https://www.cnblogs.com/yechanglv/p/6941993.html] 一、預備知識—程式的記憶體分配 一個由c/C++編譯的程式佔用的記憶體分為以下幾個部分 1、棧區(stack)— 由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其

C++:區別

C++中堆和棧的區別,自由儲存區、全域性/靜態儲存區和常量儲存區        文章來自一個論壇裡的回帖,哪個論壇記不得了!       在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態 儲存區和常量儲存區。       棧,就是那些由編譯器在需要

CC++中全域性變數,區域性變數,靜態變數常量

最近在Leecode上見到很多關於靜態變數,常量,全域性變數等相關的題,於是結合一些Leecode例題和《C++程式設計思想》整理了這篇部落格,詳細解釋了下它們的區別和用法。 1. 全域性變數    全域性變數是在所有函式體的外部定義的,程式的所有部分(其他檔案的程式碼

記憶體區劃分記憶體分配常量儲存區自由儲存區全域性區[C++][記憶體管理][轉載]

一. 在c中分為這幾個儲存區 1.棧 - 由編譯器自動分配釋放 2.堆 - 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS回收 3.全域性區(靜態區),全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一

C++記憶體分配方式-靜態儲存區常量儲存區

C++中,記憶體分為5個區:堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。棧:是由編譯器在需要時自動分配,不需要時自動清除的變數儲存區。通常存放區域性變數、函式引數等。堆:是由new分配的記憶體塊,由程式設計師釋放(編譯器不管),一般一個new與一個delete對應,一個new[]與一個del

C語言程式記憶體中:靜態儲存區的區別

一、記憶體基本構成 可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。 靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量。 棧區:在執行函式時,函式內區域性變數的儲存單元都可以在

C語言之

空間 bsp 動態 info cat malloc 分享圖片 code clu 一 局部變量存放在棧區中,函數調用結束後釋放內存空間。 #include "stdio.h"; #include "stdlib.h"; int *getNum(){ int i

C/C++靜態數據區詳解

編輯 bsp category 例如 錯誤 首地址 float 文件的 自己的 轉自:https://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html 做略微修改   本文介紹C/C++中堆,棧及

計算機中的靜態儲存區的區別

一個由C/C++編譯的程式佔用的記憶體分為以下部分:    1、棧區—由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。   2、堆區—由程式設計師分配釋放,即動態申請的區域,若程式設計師不釋放,程式結束時可能由系統回收。   3、全域性區(靜態

C/C++靜態資料區詳解(轉載只是為了查閱方便,若侵權立刪)

C/C++堆、棧及靜態資料區詳解   本文介紹C/C++中堆,棧及靜態資料區。     五大記憶體分割槽   在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。下面分別來介紹:   棧,就是那些由編譯器在需要的時候分配,在不需要

C#中的值型別與引用型別值引數引用引數輸出引數引數陣列

程式執行時,資料必須儲存在記憶體中,一個數據需要多大的記憶體、儲存的位置、如何儲存依賴於該資料的資料型別。執行中的程式使用兩個記憶體區域來儲存資料:棧和堆。 棧:                 棧是一

C++ BSS段資料段程式碼段及五大記憶體分割槽

BSS段:BSS段(bss segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態記憶體分配。 資料段:資料段(data segment)通常是指用來存放程式中已

C語言記憶體管理動態分配

昨晚整理了一晚上居然沒了?沒儲存還是沒登入我也忘了,賊心累 我捋了捋,還是得從作業系統,程序和記憶體開始理解。 程序     從作業系統的角度簡單介紹一下程序。程序是佔有資源的最小單位,這個資源當然包括記憶體。在現代作業系統中,每個程序所能訪問的記憶體是互相獨立的(一些

C#】類結構值型別引用型別里氏替換

目錄 舉例: 一、類和結構的區別: 二、棧和堆的區別: 1.官方解讀: 棧: 是編譯期間就分配好的記憶體空間,因此程式碼中必須就棧的大小有明確的定義; 區域性值型別變數、值型別引數等都在棧的記憶體中。 堆:

靜態儲存區之間的區別

一、記憶體基本構成可程式設計記憶體在基本上分為這樣的幾大部分:靜態儲存區、堆區和棧區。他們的功能不同,對他們使用方式也就不同。靜態儲存區:記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。它主要存放靜態資料、全域性資料和常量。棧區:在執行函式時,函式內