程序在編譯、運行等各個過程中,不同性質(zhì)的數(shù)據(jù)存放在不同的位置。動態(tài)內(nèi)存是從堆上分配,也叫動態(tài)內(nèi)存分配。程序員自己負責在何時釋放內(nèi)存。動態(tài)內(nèi)存的生存期由程序員決定,使用非常靈活。
C、C++程序編譯的內(nèi)存分配
1.從靜態(tài)存儲區(qū)域分配
內(nèi)存在程序編譯時就已經(jīng)分配好,這塊內(nèi)存在程序的整個運行期間都存在。速度快、不容易出錯,因為有系統(tǒng)會善后。例如全局變量,static變量等。
2.在棧上分配
在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放。棧內(nèi)存分配運算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。
3.從堆上分配
即動態(tài)內(nèi)存分配。程序在運行的時候用malloc或new申請任意大小的內(nèi)存,程序員自己負責在何時用free或delete釋放內(nèi)存。動態(tài)內(nèi)存的生存期由程序員決定,使用非常靈活。如果在堆上分配了空間,就有責任回收它,否則運行的程序會出現(xiàn)內(nèi)存泄漏,另外頻繁地分配和釋放不同大小的堆空間將會產(chǎn)生堆內(nèi)碎塊。
一個C、C++程序編譯時內(nèi)存分為5大存儲區(qū):堆區(qū)、棧區(qū)、全局區(qū)、文字常量區(qū)、程序代碼區(qū),如下表所示。
C、C++的程序編譯時內(nèi)存分配情況實例:
int a=0; //全局區(qū)初始化a
char *p1; //全局區(qū)未初始化p1
static char b; //全局區(qū)未初始化靜態(tài)變量b
int main(void)
{
int c; //棧區(qū)臨時變量c
char s[]="abc"; //棧區(qū)臨時數(shù)組變量s
char *p2; //棧區(qū)臨時變量p2
char *p3="123"; //常量區(qū)常量123,棧區(qū)指針變量p3
static int d=0; //全局靜態(tài)初始化靜態(tài)變量d
p1=new char[10]; //堆區(qū)分配10個字節(jié)符空間
p2=new char[20]; //堆區(qū)分配20個字節(jié)符空間
strcpy(p1,"123); //123放在常量區(qū),編譯器可能會將它與p3所指向的"123"優(yōu)化成一個地方
}
答案
- 棧區(qū)(stack):由編譯器自動分配釋放,存放為運行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)、返回地址等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
- 堆區(qū)(heap):一般由程序員分配釋放,若程序員不釋放,程序結(jié)束時可能由系統(tǒng)回收。分配方式類似于鏈表。
- 全局區(qū)(靜態(tài)區(qū))(static):存放全局變量、靜態(tài)數(shù)據(jù)、常量。程序結(jié)束后由系統(tǒng)釋放。
- 文字常量區(qū):常量字符串就是放在這里的。程序結(jié)束后由系統(tǒng)釋放。
- (5)程序代碼區(qū):存放函數(shù)體(類成員函數(shù)和全局函數(shù))的二進制代碼。
補充:在不同的內(nèi)存區(qū)域,對于理解編程中的數(shù)據(jù)類型作用域和注意事項,比如靜態(tài)數(shù)據(jù)和全局數(shù)據(jù)對其聲明后區(qū)域的全局可見性,動態(tài)申請的內(nèi)存為什么要及時釋放等有很大的幫助。
2 分析代碼段有沒有錯誤
代碼段1
void A(char *p)
{
p=(char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
A(str);
strcpy(str,"hello world";
printf(str);
}
代碼段2
char *A(void)
{
char p[]="hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = A();
printf(str);
}
代碼段3
void A(char **p,int num)
{
*p=(char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
A(&str,100);
strcpy(str,"hello");
printf(str);
}
代碼段4
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str,"hello";
free(str);
}
分析問題:代碼一到代碼四主要考查面試者對內(nèi)存操作的理解程度,基本功扎實的面試者能找到大部分的錯誤,但是全部找出還是有一定難度的。這四段代碼主要有以下三個問題:
代碼一:傳入函數(shù)A( char *p )的參數(shù)為字符型指針,在這個函數(shù)修改參數(shù)p的值時并不能真正修改實參的值,如:
char *str = NULL;
A(str);
執(zhí)行完這兩句后str的值仍然是NULL,如果想改變指針的值,就得用二階指針來完成。不理解指針和指針的用法是導致這個錯誤的主要原因。
代碼二:在函數(shù)A(void )中:
char p[]="hello world";
return p;
其中的p[]數(shù)組是函數(shù)A中的局部變量,函數(shù)返回后,p就被釋放掉了,str指向了一段無用的內(nèi)存區(qū)域,輸出的str會是亂碼。理解變量的作用域是解決本題的關(guān)鍵。
代碼三:避免了代碼一中的問題,A的參數(shù)是二階指針,傳入的參數(shù)也是字符串的指針的指針,這樣就可以在函數(shù)A中改變字符串指針的值了。但是在A中執(zhí)行了申請動態(tài)內(nèi)存的并且賦值給字符串指針的語句:
*p=(char *)malloc(num);
在Test中A返回后,沒有對指針*p做任何判斷就使用了p。
strcpy(str,"hello");
假如動態(tài)內(nèi)存沒有申請成功,這句就會出現(xiàn)錯誤,所以在申請動態(tài)內(nèi)存后,應該首先判斷是內(nèi)存否申請成功,然后再使用,以避免錯誤發(fā)生。如下:
if(*p =NULL)
{
.......//申請失敗異常處理
}
另外,沒有釋放動態(tài)申請的內(nèi)存空間。
代碼四:同代碼三一樣,申請了動態(tài)內(nèi)存后沒有檢驗是否申請成功就直接使用,并且在free( str)后str沒有置空,str成了“野指針”。一定要記得每次釋放動態(tài)申請的內(nèi)存空間后指針要置空,如下:
free(str);
str = NULL;
答案
四段代碼全有錯誤:
- 代碼一:A( char *p )的參數(shù)為字符型指針,在這個函數(shù)修改參數(shù)p的值時并不能真正修改實參的值。
- 代碼二:其中的p[]數(shù)組是函數(shù)A中的局部變量,函數(shù)返回后,p就被釋放掉,str便指向了一段無用的內(nèi)存區(qū)域。
- 代碼三:沒有判斷動態(tài)內(nèi)存申請是否成功而直接使用,沒有釋放動態(tài)申請的內(nèi)存,造成內(nèi)存泄漏。
- 代碼四:沒有判斷動態(tài)內(nèi)存申請是否成功而直接使用,動態(tài)內(nèi)存釋放后沒有將指針置空。
注意:申請動態(tài)內(nèi)存時一定要先判斷是否申請成功,失敗時要進行失敗處理;動態(tài)內(nèi)存使用后要及時釋放,不要造成內(nèi)存的泄漏;釋放后將原先指向動態(tài)內(nèi)存的指針置空,以免生成“野指針”。