棧:用來存放函數的形參和函數內的局部變量。由編譯器分配空間,在函數執行完後由編譯器自動釋放。
堆:用來存放由動態分配函數(如malloc)分配的空間。是由程序員自己手動分配的,並且必須由程序員使用free釋放。如果忘記用free釋放,會導致所分配的空間一直佔着不放,導致內存泄露。 堆,順序隨意。棧,後進先出(Last-In/First-Out)。
全局區(靜態區):用來存放全局變量和靜態變量。存在於程序的整個運行期間,是由編譯器分配和釋放的。
文字常量區:例如char *c = “123456”;則”123456”爲文字常量,存放於文字常量區。也由編譯器控制分配和釋放。
程序代碼區:用來存放程序的二進制代碼。
我查了下資料,有說分四個,有說分五個加一個程序代碼區,我沒查到參考的專業書籍。所以麻煩知道的告知一下,完善一下。
例子
1、
int a = 0; //全局區
void main()
{
int b; //棧
char s[] = abc; //s在棧,abc在文字常量區
char *p1,*p2; //棧
char *p3 = 123456; //123456在常量區,p3在棧上
static int c =0; //全局區
p1 = (char *)malloc(10); //p1在棧,分配的10字節在堆
p2 = (char *)malloc(20); //p2在棧,分配的20字節在堆
strcpy(p1, 123456); //123456放在常量區
}
2、
//返回char型指針
char *f()
{
//s數組存放於棧上
char s[4] = {'1','2','3','0'};
return s; //返回s數組的地址,但程序運行完s數組就被釋放了
}
void main()
{
char *s;
s = f();
printf (%s, s); //打印出來亂碼。因爲s所指向地址已經沒有數據
}
2、
內存分配方式
內存分配方式有三種:
1)從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。
2)在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,但是分配的內存容量有限。
3)從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多。
3、 常見的內存錯誤及其對策
發生內存錯誤是件非常麻煩的事情。編譯器不能自動發現這些錯誤,通常是在程序運行時才能捕捉到。而這些錯誤大多沒有明顯的症狀,時隱時現,增加了改錯的難度。有時用戶怒氣衝衝地把你找來,程序卻沒有發生任何問題,你一走,錯誤又發作了。常見的內存錯誤及其對策如下:
1) 內存分配未成功,卻使用了它。
編程新手常犯這種錯誤,因爲他們沒有意識到內存分配會不成功。常用解決辦法是,在使用內存之前檢查指針是否爲NULL。如果指針p是函數的參數,那麼在函數的入口處用assert(p!=NULL)進行檢查。如果是用malloc或new來申請內存,應該用if(p==NULL)
或if(p!=NULL)進行防錯處理。
2)內存分配雖然成功,但是尚未初始化就引用它。
犯這種錯誤主要有兩個起因:一是沒有初始化的觀念;二是誤以爲內存的缺省初值全爲零,導致引用初值錯誤(例如數組)。
內存的缺省初值究竟是什麼並沒有統一的標準,儘管有些時候爲零值,我們寧可信其無不可信其有。所以無論用何種方式創建數組,都別忘了賦初值,即便是賦零值也不可省略,不要嫌麻煩。
3)內存分配成功並且已經初始化,但操作越過了內存的邊界。
例如在使用數組時經常發生下標“多1”或者“少1”的操作。特別是在for循環語句中,循環次數很容易搞錯,導致數組操作越界。
4)忘記了釋放內存,造成內存泄露。
含有這種錯誤的函數每被調用一次就丟失一塊內存。剛開始時系統的內存充足,你看不到錯誤。終有一次程序突然死掉,系統出現提示:內存耗盡。動態內存的申請與釋放必須配對,程序中malloc與free的使用次數一定要相同,否則肯定有錯誤(new/delete同理)。
【規則1】用malloc或new申請內存之後,應該立即檢查指針值是否爲NULL。防止使用指針值爲NULL的內存。
【規則2】不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存作爲右值使用。
【規則3】注意不要返回指向“棧內存”的“指針”或者“引用”,因爲該內存在函數體結束時被自動銷燬。
【規則4】避免數組或指針的下標越界,特別要當心發生“多1”或者“少1”操作。
【規則5】動態內存的申請與釋放必須配對,防止內存泄漏。
【規則6】用free或delete釋放了內存之後,立即將指針設置爲NULL,防止產生“野指針”。
用malloc動態分配內存後一定要判斷一下分配是否成功,判斷指針的值是否爲NULL。
內存分配成功後要對內存單元進行初始化。
內存分配成功且初始化後使用時別越界了。
內存使用完後要用free(p)釋放,注意,釋放後,p的值是不會變的,仍然是一個地址值,仍然指向那塊內存區,只是這塊內存區的值變成垃圾了。爲了防止後面繼續使用這塊內存,應在free(p)後,立即p=NULL,這樣後面如果要使用,判斷p是否爲NULL時就會判斷出來。
NO.1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
請問運行Test函數後會是什麼樣的結果?
char *GetMemory(void)
{
char p[] = hello world;
retrun p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
問題同NO.1
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str,100);
strcpy(str,"hello");
printf(str);
}
問題同NO.1
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str,"hello");
free(str);
if(str != NULL)
{
strcpy(str,"world");
printf(str);
}
}
問題同NO.1
我對以上問題的分析:NO.1:程序首先申請一個char類型的指針str,並把str指向NULL(即str裏存的是NULL的地址,*str爲NULL中的值爲0),調用函數的過程中做了如下動作:
1、申請一個char 類型的指針p,
2、把str的內容copy到了p裏(這是參數傳遞過程中系統所做的),
3、爲p指針申請了100個空間,
4、返回Test函數.最後程序把字符串hello world拷貝到str指向的內存空間裏.到這裏錯誤出現了!
str的空間始終爲NULL而並沒有實際的空間.深刻理解函數調用的第2步,將不難發現問題所在!(注意:傳遞的參數和消除的參數)
NO.2:程序首先申請一個char類型的指針str,並把str指向NULL.調用函數的過程中做了如下動作:
1申請一數組p[]並將其賦值爲hello world(數組的空間大小爲12),
2返回數組名p付給str指針(即返回了數組的首地址).
那麼這樣就可以打印出字符串"hello world"了麼?當然是不能的!
因爲在函數調用的時候漏掉了最後一步.也就是在第2步return數組名後,函數調用還要進行一步操作,也就是釋放內存空間.當一個函數被調用結束後它會釋放掉它裏面所有的變量所佔用的空間.所以數組空間被釋放掉了,也就是說str所指向的內容將不確定是什麼東西.
NO.3:正確答案爲可以打印出hello.但內存泄漏了! 需要用free()函數進行釋放。NO.4:申請空間,拷貝字符串,釋放空間.前三步操作都沒有任何問題.到if語句裏的判斷條件開始出錯了,因爲一個指針被釋放之後其內容並不是NULL,而是一個不確定的值.所以if語句永遠都不能被正確執行.這也是著名的"野"指針問題.所以我們在編寫程序釋放一個指針之後一定要人爲的將指針付成NULL.這樣就會避免出現"野"指針的出現.有人說"野"指針很可怕,會帶來意想不到的錯誤.
整理的文檔csdn下載鏈接:http://download.csdn.net/detail/xqmoo8/4508702
整理的文檔百度下載鏈接:http://wenku.baidu.com/view/5de17320a5e9856a561260e0.html
參考文章1:http://hukejia.iteye.com/blog/458232
參考文章2:http://www.diybl.com/course/3_program/c/c_js/2008611/124629.html