深入探索C++對象模型之六 --- 執行期語意學

深入探索C++對象模型之六 — 執行期語意學

C++最困難的一點就在於:無法從程序源碼看出程序表達式的複雜度。因爲編譯器會在背後給你做很多的工作。對於一些表達式諸如T a = b +c,編譯器可能會創建在執行期過程創建臨時對象,那麼在程序的出口處編譯器就需要安插必要的代碼來保證創建的臨時對象都得到有效的析構。如果遇到goto、switch等會產生多個邏輯出口的,那麼每個出口處都要安插代碼。因此一般而言,我們會把object的定義儘可能放置在使用它的那個程序區段附近,減少不必要的對象產生和銷燬操作。

其實上面的一點反映在C++中就是我們可以在函數內部的任意地方定義變量,但是C程序員卻習慣而且也只能在函數起始處定義所有變量。

全局對象

c++程序中所有的global objects都是被放置在程序的data segment中。在編譯時期class object內容爲0,但是constructor需要到程序激活時纔會觸發。因此必須對一個object做靜態初始化。

另外考慮到跨平臺移植性的問題,出現了一個munch策略。

  1. 爲每一個需要靜態初始化的檔案都產生一個_sti()函數,內帶必要的constructor調用或者inline expansions。
  2. 爲每一個需要靜態內存釋放的操作產生_std()函數,內帶必要的destructor調用操作或者inline expansions。
  3. 在main函數中產生一個_main()和一個_exit()函數,其中_main()用於執行之前產生的所有的_sti()函數,而_exit()用於執行之前產生的所有的_std()函數。

具體如下圖所示:

上面提到的編譯器是如何收集一個程序中所有的_sti()和_std()的呢?這裏不介紹(每家廠商的編譯器實現方法都不盡相同)

局部靜態對象

對於局部靜態對象,我們必須要保證它的constructor和destructor都只能施行一次!!!

new和delete運算符

針對如下代碼

int *pi = new int(5)

事實上是分兩個步驟完成的:

  1. 調用函數庫的_new(int size)來分配所需要的內存
  2. 給配置而來的對象設置初值或者是調用class的constructor操作,這些都必須是在分配空間成功之後進行

上面說的是new運算符,而相對的delete運算符就會去做釋放內存空間的操作

  1. 檢測指針是否爲0
  2. 如果指針不爲0,則釋放指針指向的內存空間

對於deletedelete [],如果是要釋放數組的內存空間那麼必須使用後者,如果是前者的話編譯器會認爲是釋放第一個元素。很久以前在調用delete []的時候是需要代碼設計者自己指明元素的個數的,現在編譯器可以自己查找,即使程序員手動指明元素個數也會被編譯器忽略。

編譯器通過在數組的最前端增加一個額外的word,然後把元素書目存放在這個word裏面,來達到delete的時候確定元素個數。

另外一個要注意的是,如果使用base class的指針指向derived class的數組的話,那麼在調用delete []的話仍然會存在問題,因爲delete會按照base class的大小來釋放內存,因此我們只能迭代走過整個數組,通過把指針類型強制轉換成derived class類型的。如下:

Point *ptr = new Point3d[10]

//如下方式會按照Point的大小釋放內存,但是卻是錯誤的
delete []ptr;

//必須如下做
for(int ix=0; ix < elem_count; ++ix) {
    Point3d *p = &((Point3d*)ptr)[ix];
    delete p;
}

Placement Operate new的語意

有一個預先定義好的重載的new運算符,成爲placement operator new,它需要第二個參數,類型是void *。

Point2w *ptw = new(arena) Point2w;

其中arena指向內存中一段空間

Placement operator new只是用來分配所要求的空間,但是不調用相關對象的構造函數。因此placement operator new可以用來決定objects被放置在哪裏。

考慮如下代碼:

void foobar() {
    Point2w *p2w = new(arena) Point2w;
    //do it...
    p2w = new(arena) Point2w;
}

上面的代碼在一個已經存在的object上構造新的object,那麼即使該class object有一個destructor,該destructor也不會被調用的。

臨時性對象

在對完整表達式求值過程中,可能會產生臨時性對象,那麼摧毀臨時性對象,就必要時在完整表達式的最後一個步驟中,這就保證了該內存區域中的值已經不在被使用的時候。否則會造成該臨時性對象已經被摧毀釋放掉,但是該內容還會被訪問到,這會導致未定義的錯誤。

如果一個臨時性對象被綁定於一個reference,對象將殘留,直到被初始化之refenece的生命結束,或者直到臨時對象的生命範疇結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章