今天學習了下c/c++相關的記憶體使用量問題,大家都知道記憶體對於程式員來說是個非常神秘的空間,使用的恰當與否決定了一個程式的優劣。總之我感覺這個非常麻煩。下面是我在網上學習到的以及總結的一些東西。
一般來說,記憶體的分配方式有三種:
1.從靜態儲存地區分配。記憶體在程式編譯的時候就已經分配好,這塊記憶體在程式的整個運行期間都存在。例如全域變數,static變數。
2.在棧上建立。在執行函數時,函數內局部變數的儲存單元都可以在棧上建立,函數執行結束時這些儲存單元自動被釋放。棧記憶體配置運算內建於處理器的指令集中,效率很高,但是分配的記憶體容量有限。
3.從堆上分配,亦稱動態記憶體分配。程式在啟動並執行時候用malloc或new申請任意多少的記憶體,程式員自己負責在何時用free或delete釋放記憶體。動態記憶體的生存期由我們決定,使用非常靈活,但問題也最多。
以上三種分配方式,我們要注意記憶體生命期的問題:
1.靜態分配的地區的生命期是整個軟體運行期,就是說從軟體運行開始到軟體終止退出。只有軟體終止運行後,這塊記憶體才會被系統回收
2.在棧中分配的空間的生命期與這個變數所在的函數和類相關。如果是函數中定義的局部變數,那麼它的生命期就是函數被調用時,如果函數運行結束,那麼這塊記憶體就會被回收。如果是類中的成員變數,則它的生命期與類執行個體的生命期相同
3.在堆上分配的記憶體,生命期是從調用new或者malloc開始,到調用delete或者free結束。如果不掉用delete或者free。則這塊空間必須到軟體運行結束後才能被系統回收。
常見的記憶體錯誤及其對策如下:
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 釋放了記憶體卻繼續使用它。
有三種情況:
(1)程式中的對象調用關係過於複雜,實在難以搞清楚某個對象究竟是否已經釋放了記憶體,此時應該重新設計資料結構,從根本上解決對象管理的混亂局面。
(2)函數的return語句寫錯了,注意不要返回指向“棧記憶體”的“指標”或者“引用”,因為該記憶體在函數體結束時被自動銷毀。
(3)使用free或delete釋放了記憶體後,沒有將指標設定為NULL。導致產生“野指標”。
所以我們應該注意使用:
1.用malloc或new申請記憶體之後,應該立即檢查指標值是否為NULL。防止使用指標值為NULL的記憶體。
2.不要忘記為數組和動態記憶體賦初值。防止將未被初始化的記憶體作為右值使用。
3.避免數組或指標的下標越界,特別要當心發生“多1”或者“少1”操作。
4.動態記憶體的申請與釋放必須配對,防止記憶體流失。
5.用free或delete釋放了記憶體之後,立即將指標設定為NULL,防止產生“野指標”。
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <iostream>
#include <cassert>
using namespace std;
const int* f(const int i)
{
//i++;
cout<<i<<endl;
return &i;
}
int main()
{
char str[]="hello";
cout<<str<<endl;
char *p;
p=new char[strlen(str)+1];
if(NULL!=p) //注意判斷記憶體是否分配成功
{
strcpy(p,str);
}
char str2[]="hello";
if(!strcmp(str,str2))
{
cout<<"str = str2"<<endl;
}
cout<<p<<endl;
delete []p;
p=NULL; //防止野指標
p=str;
cout<<p<<endl;
char c=getchar();
return 0;
}
下面給出兩個經典的錯誤使用執行個體:
1.這是一個出現頻率非常高的錯誤
char* pChar = new char;
……
int a ;
pChar = &a;
……
delete pChar; //這一點要非常注意,因為pChar已經不是之前在堆上申請的記憶體了,現在已經指向了a變數,然而a又是分配在棧空間,在棧空間是不可以用delete回收的。
當然這是一個例子,具體的程式各有不同。
這段程式有兩個問題。一是pChar = &a;將導致pChar原先分配的空間無法再被擷取,就象我們的丟失了朋友的電話號碼一樣,無法再聯絡這個朋友了。這就造成了記憶體流失。如果記憶體流失多了,可能導致系統的崩潰,因為可用的資源將越來越少,直到枯竭為止。第二個問題是delete pChar將導致異常發生,因為這時的pChar已經不是指向動態分配的記憶體了,而是指向了a分配的棧空間,而棧空間是不能使用delete來回收的,因此將導致記憶體異常。
2. 返回棧記憶體指標
char *GetString(void)
{
//char *p = "hello world"; //在全域靜態區中分配空間 生存期 整個程式結束
//char *p=new char[10]; //在堆記憶體中分配記憶體空間 生存期 整個程式結束
//p="hello world";
char p[]="hello world"; //在堆棧中分配空間 函數結束 變數自動銷毀 所以變數的值就沒有了。
return p;
}
char* pGet = GetString(); //指標是有效,但返回後內容是無效的
這段程式編譯時間沒有錯誤,運行也沒有錯誤,但是你卻無法使得返回的pGet指標指向的資料是你想要的“hello world”,因為指標p的生命期是函數GetString內,運行完函數GetString後,p分配的棧空間馬上被系統回收了。雖然pGet指向了p當初分配的記憶體位址,但是那塊地址已經沒有內容了。
http://topic.csdn.net/t/20021108/12/1159482.html 這個文章裡面寫的比較不錯,可以參考一下~