程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 深度探索C++對象模型(9)

深度探索C++對象模型(9)

編輯:C++入門知識
介紹
  
  當編譯一個C++程序時,計算機的內存被分成了4個區域,一個包括程序的代碼,一個包括所有的全局變量,一個是堆棧,還有一個是堆(heap),我們稱堆是自由的內存區域,我們可以通過new和delete把對象放在這個區域。你可以在任何地方分配和釋放自由存儲區。但是要注重因為分配在堆中的對象沒有作用域的限制,因此一旦new了它,必須delete它,否則程序將崩潰,這便是內存洩漏。(C#已經通過內存托管解決了這一令人頭疼的問題)。C++通過new來分配內存,new的參數是一個表達式,該表達式返回需要分配的內存字節數,這是我以前把握的關於new的知識,下面看看通過這本書,使我們能夠更進一步的了解到些什麼。   這一章主要是說Runtime Semantics執行期語義學。   這是我們平時寫的程序片段:
   Matrix identity; //一個全局對象
  Main()
  {
  Matrix m1=identity;
  ……
  return 0;
  }   很常見的一個代碼片段,雷神從來沒有考慮過identity如何被構造,或者如何被銷毀。因為它肯定在Matrix m1=identity之前就被構造出來了,並且在main函數結束前被銷毀了。我們不用考慮這些問題,好象C++就應該這樣。但這本書是研究C++底層機制的。既然我們在看這本書,說明我們希望了解C++的編譯器又做了那些大量的工作,使得我們可以這樣使用對象。   在C++程序中所有的全局對象都被放在data segment中,假如明確賦值,則對象以該值為初值,否則所配置到內存內容為0。也就是說,假如我們有以下定義
  
  Int v1=1024;
  Int v2;
  
  則v1和v2都被配置於data segment,v1值為1024,v2值為0。(雷神在VC6環境用MFC編程時中發現假如int v2;v2的值不為0,而是-8,不知為什麼?編譯器造成的?)。   假如有一個全局對象,並且這個對象有構造函數和析構函數的話,它需要靜態的初始化操作和內存釋放工作,C++是一種跨平台的編程語言,因此它的編譯器需要一種可以移植的靜態初始化和內存釋放的方法。下面便是它的策略。
  
  1、為每一個需要靜態初始化的檔案產生一個_sit()函數,內帶構造函數或內聯的擴展。
  2、為每一個需要靜態的內存釋放操作的文件中,產生一個_std()函數,內帶析構函數或內聯的擴展。
  3、提供一個_main()函數,用來調用所有的_sti()函數,還有一個exit()函數調用所有的_std()函數。
  
  侯先生說:
  
  Sit可以理解成static initialization的縮寫。
  Std可以理解成static deallocation的縮寫。
  
  那麼main函數會被編譯器變成這樣:
   Matrix identity; //一個全局對象
  Main()
  {
  _main();//對所有的全局對象做static initialization動作。
  Matrix m1=identity;
  ……
  exit();//對所有的全局對象做static deallocation動作。
  }
  其中_main()會有一個對identity對象的靜態初始化的_sti函數,象下面偽碼這樣:
  // matrix_c是文件名編碼_identity表示靜態對象,這樣能夠保證向執行文件提供唯一的識別符號
  _sti__matrix_c_identity()
  {
  identity.Matrix:: Matrix(); //這就是靜態初始化
  }   相應的在exit()函數也會有一個_std_matrix_c_identity(),來進行static deallocation動作。
  但是被靜態初始化的對象有一些缺點,在使用異常時,對象不能被放置在try區段內。還有對象的相依順序引出的復雜度,因此不建議使用需要靜態初始化的全局對象。   局部靜態對象在C++底層機制是如何構造和在內存中銷毀的呢?
  
  1、導入一個臨時對象用來保護局部靜態對象的初始化操作。
  2、第一次處理時,臨時對象為false,於是構造函數被調用,然後臨時對象被改為true.
  3、臨時對象的true或者false便成為了判定對象是否被構造的標准。
  4、根據判定的結果決定對象的析構函數是否執行。   假如一個類定義了構造函數或者析構函數,則當你定義了一個對象數組時,編譯器會通過運行庫將你的定義進行加工,例如:
   point knots[10]; //我們的定義
  vec_new(&knots,sizeof(point),10,&point::point,0); //編譯器調用vec_new()操作。   下面給出vec_new()原型,不同的編譯器會有差別。
   void * vec_new(
  void *array, //數組的起始地址
  size_t elem_size, //每個對象的大小
  int elem_count, //數組元素個數
  void(*constrUCtor)(void*),
  void(*destructor)(void* ,char)
  )
  對於明顯獲得初值的元素,vec_new()不再有必要,例如:
  point knots[10]={
  Point(), //knots[0]
  Point(1.0,1.0,0.5), //knots[1]
  -1.0 //knots[2]
  };
  會被編譯器轉換成:
  //C++偽碼
  Point::Point(&knots[0]);
  Point::Point(&knots[1],1.0,1.0,0.5);
  Point::Point(&knots[2],-1.0,0.0,0.0);
  vec_new(&knots,sizeof(point),10,&point::point,0); //剩下的元素,編譯器調用vec_new()操作。
     怎麼樣,很神奇吧。   當編譯一個C++程序時,計算機的內存被分成了4個區域,一個包括程序的代碼,一個包括所有的全局變量,一個是堆棧,還有一個是堆(heap),我們稱堆是自由的內存區域,我們可以通過new和delete把對象放在這個區域。你可以在任何地方分配和釋放自由存儲區。但是要注重因為分配在堆中的對象沒有作用域的限制,因此一旦new了它,必須delete它,否則程序將崩潰,這便是內存洩漏。(C#已經通過內存托管解決了這一令人頭疼的問題)。C++通過new來分配內存,new的參數是一個表達式,該表達式返回需要分配的內存字節數,這是我以前把握的關於new的知識,下面看看通過這本書,使我們能夠更進一步的了解到些什麼。
  
  Point3d *origin=new Point3d; //我們new 了一個Point3d對象
  
  編譯器開始工作,上面的一行代碼被轉換成為下面的偽碼:
   Point3d * origin;
  If(origin=_new(sizeof(Point3d)))
  {
  try{
  origin=Point3d::Point3d(origin);
  }
  catch(…){
  _delete(origin);
  throw;
  }
  }   而delete origin;
  
  會被轉換成(雷神將書上的代碼改為exception handling情況):
   if(origin!=0){
  try{
  Point3d::~Point3d(origin);
  _delete(origin);
  catch(…){
  _delete(origin); //不知對否?
  throw;
  }
  }   一般來說對於new的操作都直截了當,但語言要求每一次對new的調用都必須傳回一個唯一的指針,解決這個問題的辦法是,傳回一個指針指向一個默認為size=1的內存區塊,實際上是以標准的C的malloc()來完成。同樣delete也是由標准C的free()來完成。原來如此。   最後這篇筆記再說說臨時對象的問題。
  
  T operator+(const T&,const T&); //假如我們有一個函數
  
  T a,b,c; //以及三個對象:
  
  c=a+b;
  
  //可能會導致臨時對象產生。用來放置a+b的返回值。然後再由 T的copy constructor把臨時對象當作c的初值。也有可能直接由拷貝構造將a+b的值放到c中,這時便不需要臨時對象。另外還有一種可能通過操作符的重載定義,經named return value優化也可以獲得c對象。這三種方法結果一樣,區別在於初始化的成本。對臨時對象書上有很好的總結:
  
  在某些環境下,有processor產生的臨時對象是有必要的,也是比較方便的,這樣的臨時對象由編譯器決定。
  
  臨時對象的銷毀應該是對完整表達式求值過程的最後一個步驟。
  
  因為臨時對象是根據執行期語義有條件的產生,因此它的生命規則就顯得很復雜。C++標准要求凡含有表達式執行結果的臨時對象,應該保留到對象的初始化操作完成為止。當然這樣也會有例外,當一個臨時對象被一個引用綁定時,對象將殘留,直到被初始化的引用的生命結束,或者超出臨時對象的作用域。   好了今天很有收獲,馬上就會結束這本書的學習了。下一章的標題 站在對象模型的尖端 我有些迫不及待了。
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved