天天看點

一文讀懂C語言與C++動态記憶體

程式在編譯、運作等各個過程中,不同性質的資料存放在不同的位置。動态記憶體是從堆上配置設定,也叫動态記憶體配置設定。程式員自己負責在何時釋放記憶體。動态記憶體的生存期由程式員決定,使用非常靈活。

C、C++程式編譯的記憶體配置設定

1.從靜态存儲區域配置設定

記憶體在程式編譯時就已經配置設定好,這塊記憶體在程式的整個運作期間都存在。速度快、不容易出錯,因為有系統會善後。例如全局變量,static變量等。

2.在棧上配置設定

在執行函數時,函數内局部變量的存儲單元都在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧記憶體配置設定運算内置于處理器的指令集中,效率很高,但是配置設定的記憶體容量有限。

3.從堆上配置設定

即動态記憶體配置設定。程式在運作的時候用malloc或new申請任意大小的記憶體,程式員自己負責在何時用free或delete釋放記憶體。動态記憶體的生存期由程式員決定,使用非常靈活。如果在堆上配置設定了空間,就有責任回收它,否則運作的程式會出現記憶體洩漏,另外頻繁地配置設定和釋放不同大小的堆空間将會産生堆内碎塊。

一個C、C++程式編譯時記憶體分為5大存儲區:堆區、棧區、全局區、文字常量區、程式代碼區,如下表所示。

C、C++的程式編譯時記憶體配置設定情況

執行個體:

int a=0;      //全局區初始化a
char *p1;     //全局區未初始化p1
static char b;  //全局區未初始化靜态變量b
 
int main(void)
{
  int c;      //棧區臨時變量c
  char s[]="abc";  //棧區臨時數組變量s
  char *p2;        //棧區臨時變量p2
  char *p3="123";  //常量區常量123,棧區指針變量p3
  static int d=0;   //全局靜态初始化靜态變量d
  p1=new char[10];  //堆區配置設定10個位元組符空間
  p2=new char[20];  //堆區配置設定20個位元組符空間
  strcpy(p1,"123);  //123放在常量區,編譯器可能會将它與p3所指向的"123"優化成一個地方
}

      

答案

棧區(stack):由編譯器自動配置設定釋放,存放為運作函數而配置設定的局部變量、函數參數、傳回資料、傳回位址等。其操作方式類似于資料結構中的棧。

堆區(heap):一般由程式員配置設定釋放,若程式員不釋放,程式結束時可能由系統回收。配置設定方式類似于連結清單。

全局區(靜态區)(static):存放全局變量、靜态資料、常量。程式結束後由系統釋放。

文字常量區:常量字元串就是放在這裡的。程式結束後由系統釋放。

(5)程式代碼區:存放函數體(類成員函數和全局函數)的二進制代碼。

補充:在不同的記憶體區域,對于了解程式設計中的資料類型作用域和注意事項,比如靜态資料和全局資料對其聲明後區域的全局可見性,動态申請的記憶體為什麼要及時釋放等有很大的幫助。

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);
}      

分析問題:代碼一到代碼四主要考查面試者對記憶體操作的了解程度,基本功紮實的面試者能找到大部分的錯誤,但是全部找出還是有一定難度的。這四段代碼主要有以下三個問題:

指針的了解和使用問題。

變量生存周期和作用域的問題。

動态記憶體申請和釋放的問題。

代碼一:傳入函數A( char *p )的參數為字元型指針,在這個函數修改參數p的值時并不能真正修改實參的值,如:

char *str = NULL;

A(str);

執行完這兩句後str的值仍然是NULL,如果想改變指針的值,就得用二階指針來完成。不了解指針和指針的用法是導緻這個錯誤的主要原因。

代碼二:在函數A(void )中:

char p[]="hello world";

return p;

其中的p[]數組是函數A中的局部變量,函數傳回後,p就被釋放掉了,str指向了一段無用的記憶體區域,輸出的str會是亂碼。了解變量的作用域是解決本題的關鍵。

代碼三:避免了代碼一中的問題,A的參數是二階指針,傳入的參數也是字元串的指針的指針,這樣就可以在函數A中改變字元串指針的值了。但是在A中執行了申請動态記憶體的并且指派給字元串指針的語句:

*p=(char *)malloc(num);

在Test中A傳回後,沒有對指針*p做任何判斷就使用了p。

strcpy(str,"hello");

假如動态記憶體沒有申請成功,這句就會出現錯誤,是以在申請動态記憶體後,應該首先判斷是記憶體否申請成功,然後再使用,以避免錯誤發生。如下:

if(*p =NULL)

{

   .......//申請失敗異常處理

}

另外,沒有釋放動态申請的記憶體空間。

代碼四:同代碼三一樣,申請了動态記憶體後沒有檢驗是否申請成功就直接使用,并且在free( str)後str沒有置空,str成了“野指針”。一定要記得每次釋放動态申請的記憶體空間後指針要置空,如下:

free(str);

str = NULL;

四段代碼全有錯誤:

代碼一:A( char *p )的參數為字元型指針,在這個函數修改參數p的值時并不能真正修改實參的值。

代碼二:其中的p[]數組是函數A中的局部變量,函數傳回後,p就被釋放掉,str便指向了一段無用的記憶體區域。

代碼三:沒有判斷動态記憶體申請是否成功而直接使用,沒有釋放動态申請的記憶體,造成記憶體洩漏。

代碼四:沒有判斷動态記憶體申請是否成功而直接使用,動态記憶體釋放後沒有将指針置空。

注意:申請動态記憶體時一定要先判斷是否申請成功,失敗時要進行失敗處理;動态記憶體使用後要及時釋放,不要造成記憶體的洩漏;釋放後将原先指向動态記憶體的指針置空,以免生成“野指針”。

繼續閱讀