1. 程式人生 > >c語言之記憶體使用

c語言之記憶體使用

c 記憶體分配

1、程式程式碼區:存放函式體的二進位制程式碼。

2、全域性區資料區:全域性資料區劃分為三個區域。

     全域性變數和靜態變數的儲存是放在一塊的。初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。常量資料存放在另一個區域裡。這些資料在程式結束後由系統釋放。我們所說的BSS段(bss segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。

3、棧區:由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。

4、堆區:一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS回收。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列,呵呵。

5、命令列引數區:存放命令列引數和環境變數的值。

關於區域性的字串常量是存放在全域性的常量區還是棧區,不同的編譯器有不同的實現。可以通過組合語言察看一下。不過vc環境下,區域性常量就像區域性變數一樣儲存於棧中,全域性常量、字元常量儲存於文字常量區。TC在常量區。

例子:

int a = 0; //靜態儲存區(初始化區域)

char *p1; //靜態儲存區(未初始化區域)

void example()

{

int b; //

char s[] = “abc”; //

char *p2; //

static int c =0; //靜態儲存區(初始化區域)

//分配得來的1020位元組的區域就在堆上

p1 = (char *)malloc(10);

p2 = (char *)malloc(20);

}

在嵌入式系統中有ROMRAM兩類記憶體,程式被固化進ROM,變數和堆疊設在RAM中,用const定義的常量也會被放入ROM

注:const定義常量可以節省空間,避免不必要的記憶體分配

#define PI 3.14159//常量巨集

const double g_pi = 3.14159;//此時並未將g_pi放入ROM

......

double a = g_pi;

//此時為g_pi分配記憶體,以後不再分配!

double b = PI;//編譯期間進行巨集替換,分配記憶體

double c = g_pi;//沒有記憶體分配

double d = PI;//再進行巨集替換,又一次分配記憶體!

在linux下:可以通過引數-c來編譯生成彙編檔案。如: gcc -c *.c
 gcc *.o -Map test.txt -o test.elf
用文字編輯器檢視test.txt檔案,你就看到那些bss段,data段,text段等資訊了,但是沒有堆疊段相關資訊,用objdump命令檢視.o檔案的反彙編後的資訊,或者用gcc -S *.c,檢視各個.S檔案就明白了。

文章二、記憶體的規劃種類

1)常規內(Conventional Memory)在記憶體分配表中佔用最前面的位置,從0KB到640KB(地址000000H~109FFFFH),共佔640KB的容量。因為它在記憶體的最前面並且在DOS可管理的記憶體區,我們又稱之為Low Dos Memory(低DOS記憶體),或稱為基本記憶體(Base Memory),使用此空間的程式有BIOS、DOS作業系統、外圍裝置的驅動程式、中斷向量表、一些常駐的程式、空閒可用的記憶體空間、以及一般的應用軟體都可在此空間執行。

2)高位記憶體(UM)是英文Upper Memory的縮寫,是常規記憶體上面的一層記憶體(640KB~1024KB)。   3)高階記憶體區(HMA)是英文High Memory Area的縮寫。它是1024KB至1088KB之間的64KB記憶體,管為高階記憶體區,其地址為100000H~10FFEFH或以上,CPU在實地址模式下以Segment:OFFSET(段地址:偏移量)方式來定址,其定址的最大邏輯記憶體空間為(FFFF:FFFF),即10FFEFH。  
4)EMB是英文Extended Memory Block(擴充套件記憶體塊)的縮寫,早期採用的擴充儲存器(EPM)必須遵循EMS規範(如使用EMM386.exe),後來使用的擴充套件儲存器(EXM)必須遵循XMS規範(如使用Himem.sys)。擴充套件記憶體是指1MB以上的記憶體空間,其地址是從100000H開始,連續不斷向上擴充套件的記憶體,擴充套件記憶體取決於CPU的定址能力。

1、記憶體分配方式

  記憶體分配方式有三種:

  (1)從靜態儲存區域分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個執行期間都存在。例如全域性變數,static變數。

  (2)在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率很高,但是分配的記憶體容量有限。

  (3) 從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,程式設計師自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。

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

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

  * 記憶體分配未成功,卻使用了它。

  程式設計新手常犯這種錯誤,因為他們沒有意識到記憶體分配會不成功。常用解決辦法是,在使用記憶體之前檢查指標是否為NULL。如果指標p是函式的引數,那麼在函式的入口處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請記憶體,應該用if(p==NULL) 或if(p!=NULL)進行防錯處理。

  * 記憶體分配雖然成功,但是尚未初始化就引用它。

  犯這種錯誤主要有兩個起因:

                   一是沒有初始化的觀念;

                  二是誤以為記憶體的預設初值全為零,導致引用初值錯誤(例如陣列)。記憶體的預設初值究竟是什麼並沒有統一的標準,儘管有些時候為零值,我們寧可信其無不可信其有。所以無論用何種方式建立陣列,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。

  * 記憶體分配成功並且已經初始化,但操作越過了記憶體的邊界。

  例如在使用陣列時經常發生下標“多1”或者“少1”的操作。特別是在for迴圈語句中,迴圈次數很容易搞錯,導致陣列操作越界。

  * 忘記了釋放記憶體,造成記憶體洩露。

  含有這種錯誤的函式每被呼叫一次就丟失一塊記憶體。剛開始時系統的記憶體充足,你看不到錯誤。終有一次程式突然死掉,系統出現提示:記憶體耗盡。

  動態記憶體的申請與釋放必須配對,程式中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。

  * 釋放了記憶體卻繼續使用它。
 
  有三種情況:

  (1)程式中的物件呼叫關係過於複雜,實在難以搞清楚某個物件究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決物件管理的混亂局面。

  (2)函式的return語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函式體結束時被自動銷燬。

  (3)使用free或delete釋放了記憶體後,沒有將指標設定為NULL。導致產生“野指標”。

  【規則1】用malloc或new申請記憶體之後,應該立即檢查指標值是否為NULL。防止使用指標值為NULL的記憶體。

  【規則2】不要忘記為陣列和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。

  【規則3】避免陣列或指標的下標越界,特別要當心發生“多1”或者“少1”操作。

  【規則4】動態記憶體的申請與釋放必須配對,防止記憶體洩漏。

  【規則5】用free或delete釋放了記憶體之後,立即將指標設定為NULL,防止產生“野指標”。

  3、指標與陣列的對比

  C /C程式中,指標和陣列在不少地方可以相互替換著用,讓人產生一種錯覺,以為兩者是等價的。

  陣列要麼在靜態儲存區被建立(如全域性陣列),要麼在棧上被建立。陣列名對應著(而不是指向)一塊記憶體,其地址與容量在生命期內保持不變,只有陣列的內容可以改變。

  指標可以隨時指向任意型別的記憶體塊,它的特徵是“可變”,所以我們常用指標來操作動態記憶體。指標遠比陣列靈活,但也更危險。

  下面以字串為例比較指標與陣列的特性。

  3.1 修改內容

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

  1. char a[] = “hello”;
  2. a[0] = ‘X’;
  3. cout << a << endl;
  4. char *p = “world”; // 注意p指向常量字串
  5. p[0] = ‘X’; // 編譯器不能發現該錯誤
  6. cout << p << endl;
複製程式碼 示例3.1 修改陣列和指標的內容

  3.2 內容複製與比較

  不能對陣列名進行直接複製與比較。示例7-3-2中,若想把陣列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. char a[] = "hello";
  3. char b[10];
  4. strcpy(b, a); // 不能用 b = a;
  5. if(strcmp(b, a) == 0) // 不能用 if (b == a)

  6. // 指標…
  7. int len = strlen(a);
  8. char *p = (char *)malloc(sizeof(char)*(len 1));
  9. strcpy(p,a); // 不要用 p = a;
  10. if(strcmp(p, a) == 0) // 不要用 if (p == a)
複製程式碼 示例3.2 陣列和指標的內容複製與比較

  3.3 計算記憶體容量

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

  注意當陣列作為函式的引數進行傳遞時,該陣列自動退化為同類型的指標。示例7-3-3(b)中,不論陣列a的容量是多少,sizeof(a)始終等於sizeof(char *)。
  1. char a[] = "hello world";
  2. char *p = a;
  3. cout<< sizeof(a) << endl; // 12位元組
  4. cout<< sizeof(p) << endl; // 4位元組
複製程式碼 示例3.3(a) 計算陣列和指標的記憶體容量
  1. void Func(char a[100])
  2. {
  3.  cout<< sizeof(a) << endl; // 4位元組而不是100位元組
  4. }
複製程式碼 示例3.3(b) 陣列退化為指標


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

  如果函式的引數是一個指標,不要指望用該指標去申請動態記憶體。示例7-4-1中,Test函式的語句GetMemory(str, 200)並沒有使str獲得期望的記憶體,str依舊是NULL,為什麼?
  1. void GetMemory(char *p, int num)
  2. {
  3.  p = (char *)malloc(sizeof(char) * num);
  4. }
  5. void Test(void)
  6. {
  7.  char *str = NULL;
  8.  GetMemory(str, 100); // str 仍然為 NULL
  9.  strcpy(str, "hello"); // 執行錯誤
  10. }
複製程式碼 示例4.1 試圖用指標引數申請動態記憶體

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

  如果非得要用指標引數去申請記憶體,那麼應該改用“指向指標的指標”,見示例4.2。
  1. void GetMemory2(char **p, int num)
  2. {
  3.  *p = (char *)malloc(sizeof(char) * num);
  4. }
  5. void Test2(void)
  6. {
  7.  char *str = NULL;
  8.  GetMemory2(&str, 100); // 注意引數是 &str,而不是str
  9.  strcpy(str, "hello");
  10.  cout<< str << endl;
  11.  free(str);
  12. }
複製程式碼 示例4.2用指向指標的指標申請動態記憶體

  由於“指向指標的指標”這個概念不容易理解,我們可以用函式返回值來傳遞動態記憶體。這種方法更加簡單,見示例4.3。
  1. char *GetMemory3(int num)
  2. {
  3.  char *p = (char *)malloc(sizeof(char) * num);
  4.  return p;
  5. }
  6. void Test3(void)
  7. {
  8.  char *str = NULL;
  9.  str = GetMemory3(100);
  10.  strcpy(str, "hello");
  11.  cout<< str << endl;
  12.  free(str);
  13. }
複製程式碼 示例4.3 用函式返回值來傳遞動態記憶體

  用函式返回值來傳遞動態記憶體這種方法雖然好用,但是常常有人把return語句用錯了。這裡強調不要用return語句返回指向“棧記憶體”的指標,因為該記憶體在函式結束時自動消亡,見示例4.4。
  1. char *GetString(void)
  2. {
  3.  char p[] = "hello world";
  4.  return p; // 編譯器將提出警告
  5. }
  6. void Test4(void)
  7. {
  8.  char *str = NULL;
  9.  str = GetString(); // str 的內容是垃圾
  10.  cout<< str << endl;
  11. }
複製程式碼 示例4.4 return語句返回指向“棧記憶體”的指標

  用偵錯程式逐步跟蹤Test4,發現執行str = GetString語句後str不再是NULL指標,但是str的內容不是“hello world”而是垃圾。
如果把示例4.4改寫成示例4.5,會怎麼樣?
  1. char *GetString2(void)
  2. {
  3.  char *p = "hello world";
  4.  return p;
  5. }
  6. void Test5(void)
  7. {
  8.  char *str = NULL;
  9.  str = GetString2();
  10.  cout<< str << endl;
  11. }
複製程式碼 示例4.5 return語句返回常量字串

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

  5、杜絕“野指標”

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

  (1)指標變數沒有被初始化。任何指標變數剛被建立時不會自動成為NULL指標,它的預設值是隨機的,它會亂指一氣。所以,指標變數在建立的同時應當被初始化,要麼將指標設定為NULL,要麼讓它指向合法的記憶體。例如
  1. char *p = NULL;
  2. char *str = (char *) malloc(100);
複製程式碼 (2)指標p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指標。

  (3)指標操作超越了變數的作用範圍。這種情況讓人防不勝防,示例程式如下:
  1. class A
  2. {
  3.  public:
  4.   void Func(void){ cout << “Func of class A” << endl; }
  5. };
  6. void Test(void)
  7. {
  8.  A *p;
  9.  {
  10.   A a;
  11.   p = &a; // 注意 a 的生命期
  12.  }
  13.  p->Func(); // p是“野指標”
  14. }
複製程式碼 函式Test在執行語句p->Func()時,物件a已經消失,而p是指向a的,所以p就成了“野指標”。但奇怪的是我執行這個程式時居然沒有出錯,這可能與編譯器有關。


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

  malloc與free是C /C語言的標準庫函式,new/delete是C 的運算子。它們都可用於申請動態記憶體和釋放記憶體。

  對於非內部資料型別的物件而言,光用maloc/free無法滿足動態物件的要求。物件在建立的同時要自動執行建構函式,物件在消亡之前要自動執行解構函式。由於malloc/free是庫函式而不是運算子,不在編譯器控制權限之內,不能夠把執行建構函式和解構函式的任務強加於malloc/free。

   因此C 語言需要一個能完成動態記憶體分配和初始化工作的運算子new,以及一個能完成清理與釋放記憶體工作的運算子delete。注意new/delete不是庫函式。我們先看一看malloc/free和new/delete如何實現物件的動態記憶體管理,見示例6。
  1. class Obj
  2. {
  3.  public :
  4.   Obj(void){ cout << “Initialization” << endl; }
  5.   ~Obj(void){ cout << “Destroy” << endl; }
  6.   void Initialize(void){ cout << “Initialization” << endl; }
  7.   void Destroy(void){ cout << “Destroy” << endl; }
  8. };
  9. void UseMallocFree(void)
  10. {
  11.  Obj *a = (obj *)malloc(sizeof(obj)); // 申請動態記憶體
  12.  a->Initialize(); // 初始化
  13.  //…
  14.  a->Destroy(); // 清除工作
  15.  free(a); // 釋放記憶體
  16. }
  17. void UseNewDelete(void)
  18. {
  19.  Obj *a = new Obj; // 申請動態記憶體並且初始化
  20.  //…
  21.  delete a; // 清除並且釋放記憶體
  22. }
複製程式碼 示例6 用malloc/free和new/delete如何實現物件的動態記憶體管理

  類Obj的函式Initialize模擬了建構函式的功能,函式Destroy模擬了解構函式的功能。函式UseMallocFree中,由於 malloc/free不能執行建構函式與解構函式,必須呼叫成員函式Initialize和Destroy來完成初始化與清除工作。函式 UseNewDelete則簡單得多。

  所以我們不要企圖用malloc/free來完成動態物件的記憶體管理,應該用new/delete。由於內部資料型別的“物件”沒有構造與析構的過程,對它們而言malloc/free和new/delete是等價的。

  既然new/delete的功能完全覆蓋了malloc/free,為什麼C 不把malloc/free淘汰出局呢?這是因為C 程式經常要呼叫C函式,而C程式只能用malloc/free管理動態記憶體。

  如果用free釋放“new建立的動態物件”,那麼該物件因無法執行解構函式而可能導致程式出錯。如果用delete釋放“malloc申請的動態記憶體 ”,理論上講程式不會出錯,但是該程式的可讀性很差。所以new/delete必須配對使用,malloc/free也一樣。

  7、記憶體耗盡怎麼辦?

  如果在申請動態記憶體時找不到足夠大的記憶體塊,malloc和new將返回NULL指標,宣告記憶體申請失敗。通常有三種方式處理“記憶體耗盡”問題。

  (1)判斷指標是否為NULL,如果是則馬上用return語句終止本函式。例如:
  1. void Func(void)
  2. {
  3.  A *a = new A;
  4.  if(a == NULL)
  5.  {
  6.   return;
  7.  }
  8.  …
  9. }
複製程式碼 (2)判斷指標是否為NULL,如果是則馬上用exit(1)終止整個程式的執行。例如:
  1. void Func(void)
  2. {
  3.  A *a = new A;
  4.  if(a == NULL)
  5.  {
  6.   cout << “Memory Exhausted” << endl;
  7.   exit(1);
  8.  }
  9.  …
  10. }
複製程式碼 (3)為new和malloc設定異常處理函式。例如Visual C 可以用_set_new_hander函式為new設定使用者自己定義的異常處理函式,也可以讓malloc享用與new相同的異常處理函式。詳細內容請參考C 使用手冊。

  上述(1)(2)方式使用最普遍。如果一個函式內有多處需要申請動態記憶體,那麼方式(1)就顯得力不從心(釋放記憶體很麻煩),應該用方式(2)來處理。

  很多人不忍心用exit(1),問:“不編寫出錯處理程式,讓作業系統自己解決行不行?”

  不行。如果發生“記憶體耗盡”這樣的事情,一般說來應用程式已經無藥可救。如果不用exit(1) 把壞程式殺死,它可能會害死作業系統。道理如同:如果不把歹徒擊斃,歹徒在老死之前會犯下更多的罪。

  有一個很重要的現象要告訴大家。對於32位以上的應用程式而言,無論怎樣使用malloc與new,幾乎不可能導致“記憶體耗盡”。我在Windows 98下用Visual C 編寫了測試程式,見示例7。這個程式會無休止地執行下去,根本不會終止。因為32位作業系統支援“虛存”,記憶體用完了,自動用硬碟空間頂替。我只聽到硬碟嘎吱嘎吱地響,Window 98已經累得對鍵盤、滑鼠毫無反應。

  我可以得出這麼一個結論:對於32位以上的應用程式,“記憶體耗盡”錯誤處理程式毫無用處。這下可把Unix和Windows程式設計師們樂壞了:反正錯誤處理程式不起作用,我就不寫了,省了很多麻煩。

  我不想誤導讀者,必須強調:不加錯誤處理將導致程式的質量很差,千萬不可因小失大。
  1. void main(void)
  2. {
  3.  float *p = NULL;
  4.  while(TRUE)
  5.  {
  6.   p = new float[1000000];
  7.   cout << “eat memory” << endl;
  8.   if(p==NULL)
  9.    exit(1);
  10.  }
  11. }
複製程式碼 示例7試圖耗盡作業系統的記憶體


  8、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函式本身並不識別要申請的記憶體是什麼型別,它只關心記憶體的總位元組數。我們通常記不住int, float等資料型別的變數的確切位元組數。例如int變數在16位系統下是2個位元組,在32位下是4個位元組;而float變數在16位系統下是4個位元組,在32位下也是4個位元組。最好用以下程式作一次測試:
  1. cout << sizeof(char) << endl;
  2. cout << sizeof(int) << endl;
  3. cout << sizeof(unsigned int) << endl;
  4. cout << sizeof(long) << endl;
  5. cout << sizeof(unsigned long) << endl;
  6. cout << sizeof(float) << endl;
  7. cout << sizeof(double) << endl;
  8. cout << sizeof(void *) << endl;
複製程式碼 在malloc的“()”中使用sizeof運算子是良好的風格,但要當心有時我們會昏了頭,寫出 p = malloc(sizeof(p))這樣的程式來。

  * 函式free的原型如下:
  1. void free( void * memblock );
複製程式碼 為什麼free 函式不象malloc函式那樣複雜呢?這是因為指標p的型別以及它所指的記憶體的容量事先都是知道的,語句free(p)能正確地釋放記憶體。如果p是 NULL指標,那麼free對p無論操作多少次都不會出問題。如果p不是NULL指標,那麼free對p連續操作兩次就會導致程式執行錯誤。

  9、new/delete 的使用要點

  運算子new使用起來要比函式malloc簡單得多,例如:
  1. int *p1 = (int *)malloc(sizeof(int) * length);
  2. int *p2 = new int[length];
複製程式碼 這是因為new內建了sizeof、型別轉換和型別安全檢查功能。對於非內部資料型別的物件而言,new在建立動態物件的同時完成了初始化工作。如果物件有多個建構函式,那麼new的語句也可以有多種形式。例如
  1. class Obj
  2. {
  3.  public :
  4.   Obj(void); // 無引數的建構函式
  5.   Obj(int x); // 帶一個引數的建構函式
  6.   …
  7. }
  8. void Test(void)
  9. {
  10.  Obj *a = new Obj;
  11.  Obj *b = new Obj(1); // 初值為1
  12.  …
  13.  delete a;
  14.  delete b;
  15. }
複製程式碼 如果用new建立物件陣列,那麼只能使用物件的無引數建構函式。例如
  1. Obj *objects = new Obj[100]; // 建立100個動態物件
複製程式碼 不能寫成
  1. Obj *objects = new Obj[100](1);// 建立100個動態物件的同時賦初值1
複製程式碼 在用delete釋放物件陣列時,留意不要丟了符號‘[]’。例如
  1. delete []objects; // 正確的用法
  2. delete objects; // 錯誤的用法
複製程式碼 後者相當於delete objects[0],漏掉了另外99個物件。

  10、一些心得體會

  我認識不少技術不錯的C /C程式設計師,很少有人能拍拍胸脯說通曉指標與記憶體管理(包括我自己)。我最初學習C語言時特別怕指標,導致我開發第一個應用軟體(約1萬行C程式碼)時沒有使用一個指標,全用陣列來頂替指標,實在蠢笨得過分。躲避指標不是辦法,後來我改寫了這個軟體,程式碼量縮小到原先的一半。

  我的經驗教訓是:

  (1)越是怕指標,就越要使用指標。不會正確使用指標,肯定算不上是合格的程式設計師。

  (2)必須養成“使用偵錯程式逐步跟蹤程式”的習慣,只有這樣才能發現問題的本質。