1.常見的內存錯誤及對策
發生內存錯誤是件非常麻煩 的事情。編譯器不能自動發現這些錯誤,通常是在程序運行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時用戶怒氣衝衝地把
你找來,程序卻沒有發生任何問題,你一走,錯誤又發作了。 常見的內存錯誤及其對策如下:
(1) 內存分配未成功,卻使用了它。
編程新手常犯這種錯誤,因爲他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查指針是否爲NULL。如果指針p是函數的參數,那麼在 函數的入口處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請內存,應該用if(p==NULL)
或if(p!=NULL)進行防錯處理。
(2)內存分配雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個 起因:一是沒有初始化的觀念;二是誤以爲內存的缺省初值全爲零,導致引用初值錯誤(例如數組)。內存的缺省初值究竟是什麼並沒有統一的標準,儘管有些時候 爲零值,我們寧可信其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
(3)內存分配成功並且已經初始化,但操作越過了內存的邊界。
例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for循 環語句中,循環次數很容易搞錯,導致數組操作越界。
(4)忘記了釋放內存,造成內存泄露。
含有這種錯誤的函數每被 調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。動態內存的申請與
釋放必須配對,程序中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。
(5)釋放了內存卻繼續使用它(所謂的野指針)。一般情況下,釋放掉指針後將指針置爲空。
2.指針與數組對比
C++/C程序中,指針和數組在不少地方可以相互替換着用,讓人產生一種錯覺,以爲兩者是等價的。數組要麼在靜態存儲區被創建(如全局數組),要麼在棧上被創建。數組名對應着(而不是指向)一塊內存,其地址與容量在生命期內保持不變,只有數組的內容 可以改變。指針可以隨時指向任意類型的內存塊,它的特徵是“可變”,所以我們常用指針來操作動態內存。指針遠比數組靈活,但也更危險。
例子1:
字符數組a的 容量是6個字符,其內容爲hello。a的內容可以改變,如a[0]=
'X’。指針p指向常量字符串“world”(位於靜態存儲區,內容爲world),常量字符串的內容是不可以被修改的。從語法上看,編譯器並不覺得語句 p[0]= 'X’有什麼不妥,但是該語句企圖修改常量字符串的內容而導致運行錯誤。
- char a[] = “hello”;
- a[0] = 'X’;
- cout << a << endl;
- char *p = “world”; // 注意p指向常量字符串
- p[0] = 'X’; // 編譯器不能發現該錯誤
- cout << p << endl; <span style="font-size: 14px; line-height: 22.383333206176758px; color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋體; "> </span>
例子2:
用運算符sizeof可以計算出數組的容量(字節數)。注意當數組作爲函數的參數進行傳遞時,該數組自動退化爲 同類型的指針。不論數組a的容量是多少,sizeof(a)始終等於指針的容量
- char a[] = "hello world";
- char *p = a;
- cout<< sizeof(a) << endl; // 12字節(注意別忘了加上末尾的‘\0’)
- cout<< sizeof(p) << endl; // 4字節 示例3.3(a) 計算數組和指針的內存容量
- void Func(char a[100])
- {
- cout<< sizeof(a) << endl; // 4字節而不是100字節
- }
3.指針參數傳遞內存
用函數返回值來傳遞動態內存這種方法雖然好用,但是常常有人把return語句用錯了。(下面的用法是對的)
- 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);
- } <span style="color: rgb(51, 51, 51); font-family: Verdana, Arial, Helvetica, sans-serif, 宋體; font-size: 14px; line-height: 22.383333206176758px; "> </span>
這裏強調不要用 return語句返回指向“棧內存”的指針,因爲該內存在函數結束時自動消亡。(下面就是錯的)
- 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”而是垃圾。
如果把示例改成下面這樣
- char *GetString2(void)
- {
- char *p = "hello world";
- return p;
- }
- void Test5(void)
- {
- char *str = NULL;
- str = GetString2();
- cout<< str << endl;
- }
4.杜絕野指針
野指針”不是NULL指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針,因爲用if語句很容易 判斷。但是“野指針”是很危險的,if語句對它不起作用。 “野指針”的成因主要有兩種:
- 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是“野指針”
- }