靜態物件如何初始化?

2021-04-12 17:18:52 字數 3774 閱讀 2946

介紹

當編譯乙個c++程式時,計算機的記憶體被分成了4個區域,乙個包括程式的**,乙個包括所有的全域性變數,乙個是堆疊,還有乙個是堆(heap),我們稱堆是自由的記憶體區域,我們可以通過new和delete把物件放在這個區域。你可以在任何地方分配和釋放自由儲存區。但是要注意因為分配在堆中的物件沒有作用域的限制,因此一旦new了它,必須delete它,否則程式將崩潰,這便是記憶體洩漏。(c#已經通過記憶體託管解決了這一令人頭疼的問題)。c++通過new來分配記憶體,new的引數是乙個表示式,該表示式返回需要分配的記憶體位元組數,這是我以前掌握的關於new的知識。

正文這一章主要是說runtime semantics執行期語義學。

這是我們平時寫的程式片段:

matrix identity; //乙個全域性物件

main()

很常見的乙個**片段,雷神從來沒有考慮過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()會有乙個對identity物件的靜態初始化的_sti函式,象下面偽碼這樣:

// matrix_c是檔名編碼_identity表示靜態物件,這樣能夠保證向執行檔案提供唯一的識別符號

_sti__matrix_c_identity()

相應的在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]=;

會被編譯器轉換成:

//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)))

catch(…)

}而delete origin;

會被轉換成(雷神將書上的**改為exception handling情況):

if(origin!=0)

}一般來說對於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 父在前子在後 先將父類的物件例項出來後,進行子類...

靜態成員資料 靜態物件初始化

1 靜態成員資料的定義,與靜態型別的變數的定義方式一樣,要在成員資料的定義之前加關鍵字static。2 靜態成員資料必須有確定的值,但由於在類的定義中不能對成員資料直接進行初始化,故必須在類定義的外部對靜態成員資料再宣告一次,並進行初始化,此時,前面不需要加關鍵字static。同時為了保持靜態成員資...

物件初始化

在oc中使用alloc後,會分配到一塊記憶體塊,同時這塊記憶體塊會被清零,所以初始化函式init是不需要再做置零的操作的。在oc中,應該在alloc後就呼叫初始化函式init,使用new可以達到這個效果,但new不是oc的風格。物件在init時,應該先使用其父類的init,然後再使用自己的init行...