C 物件模型筆記 物件的三種記憶體布局

2021-05-21 23:46:51 字數 3007 閱讀 4213

下面的c++**定義了乙個類point:

view plaincopy to clipboardprint?

class point  

;  class point

; 現在定義point的乙個物件pt:point pt; 這個筆記討論的問題就是:如果讓你設計這個物件的記憶體布局,你會怎麼設計它?(記憶體布局在這裡指的是物件各個成員在記憶體的排放)

下面給出lippman在《深度探索c++物件模型》中提出的三種可能的記憶體布局。

一、簡單的方式(原文是:****** object model)

point pt;如下圖所示:

看著上面的圖,疑問就來了:「簡單在**啊,簡單在**?」,簡單在編譯器設計者的眼睛裡…從編譯器設計者的角度來說,這種設計是比較簡單的,符合他們偷懶的習慣,因為每個物件一出來,有幾個成員,就分配幾個指標就可以了,比如pt,有7個成員(下面再討論有繼承的情況),所以就有7個指標,如果是資料成員,那麼就指向實際的資料;如果是成員函式,就指向該函式的入口位址。而且,這種做法還有第二個好處,那就是避免了不同大小的成員有不同的儲存空間這麼乙個東西。什麼意思呢?比如有乙個類有兩個資料成員:char,int的,char佔乙個位元組,而int佔4位元組,但是我用的是32位機,所以預設情況是4位元組對齊的(關於結構體的對齊方式,請google相關的資料),所以編譯器為物件分配空間大小的時候,還得考慮這些情況,多累啊…現在的這個模型,很好,反正每個指標都是4位元組,就沒有什麼對齊不對齊的說法了。那對於編譯器的設計者們就簡單多了。

下面來看看這種模型怎麼用於繼承和多型的例子(是我的討論,書上寫的不是很詳細)

比如下面的**:

+ expand sourceview plaincopy to clipboardprint?

class point2d : public point  

;  class point2d : public point

;那麼現在定義乙個point2d的物件pt2d,這個物件的記憶體布局又是怎麼通過上面的所謂簡單的記憶體布局體現出來的呢?

有以下兩種方法:

1、在pt2d中,再分配多乙個指標,指向基類的布局。

下面我們來看看這樣的布局怎麼實現多型?

以上面的虛函式print為例子,如以上的**。我的辦法就是:呼叫虛函式的時候,先查詢派生類的部分,有沒有指向print的指標(在本例中,有);如果有,就呼叫派生類的,如果沒有,再到基類那邊去找。

是不是感到有些眼熟啊?比如在mfc訊息對映的實現**中…可以參考在下寫的「mfc訊息對映原理」

2、對於派生類的第二種布局就是把基類的成員直接嵌入派生類裡面去。

關於多型的實現也是通過類似於上面的第一種派生布局的方法來實現的,即先找派生類的,再找基類的。

其實,這種布局還有第三個優點,那就是減少了編譯依賴,比如突然有一天,你要在類裡面改乙個成員的名字,或者改乙個成員函式的實現,這種布局,只需要重新編譯類的實際定義的地方就可以了,關於類的物件及其使用的地方,都可以不用編譯(不過對於增加或者減少成員的時候就不行)

優點說完了,好像也挺好的;不過根據中國的偉大的陰陽學說,下面該說缺點了:

不知看到了上面的三個優點,你發現了什麼?不知道有沒有看見,漏了乙個重要的東西,那就是---效率;效率是c++的最重要的指標之一(c++的兩條最重要的指標:與c相容,追上c的效率);所以,缺點就是沒有效率。

具體分析一下,每個物件都放著指向本類的成員函式的指標,這樣會造成大量的重複。多型的時候,還得先找派生類,再找基類,會浪費了大量的時間;又浪費時間又浪費空間的做法,c++怎麼可以接受呢?所以這種記憶體布局不出現在實際的c++編譯當中。但是,這種做法啟發了lippman他們,發明了:指向物件成員的指標

二、**驅動(table-driven)的記憶體布局

還是上面的point類的物件pt,看一下這種模型是什麼東東?

**驅動的記憶體模型,比起上面的第一種布局,還多了乙個間接層,那樣做的好處就是:在擴充套件成員的時候,pt本身的布局沒有變,還是兩個**,變的只是定義的東西,編譯器就可能會根據這些情況來優化布局,使得減少編譯依賴。

缺點嘛,不用說,上面的那個簡單的記憶體布局都已經是低效而不為c++所接收的了,更何況這個,馬上就淘汰出局了。不過這個模型也給lippman一些啟發:發明了虛函式表。

關於這個模型在繼承的情況下究竟是如何的?下面畫個圖看看:

關於**驅動的簡介:

順便說一句,其實mfc裡面的訊息對映的做法,與其說是參考第一種布局,還不如說參考第二種布局,用的就是**驅動(類似的還有qt的訊號與槽,vcl的事件機制等等)

三、c++中真實的物件的記憶體布局

千呼萬喚始出來,終於到正題了。說道這裡,想起一句經典:「美女這東西,就像鮮花一樣,需要綠葉的陪襯才顯示出她的嬌美」~

綠葉已經介紹完畢,下面看鮮花,呵呵。

下面的重點在於解釋一下這個記憶體布局的特點:

1、非靜態(nonstatic)資料成員,每個物件的記憶體空間裡面有乙份。

2、靜態資料成員,靜態與非靜態的成員函式,在整個記憶體裡面只有乙份實體。

3、定義了虛函式的每乙個類,都有乙份虛函式表;表項裡面放的是這個類的相關的函式呼叫(等一下畫個關於繼承的看看就知道了)

4、每個定義了虛函式的類的物件裡面都有乙個指標(像上面的_vptr_point),指向本類的虛函式表。

注意:1)、如果有定義了兩個物件point pt1; point pt2; 那麼pt1,和pt2的_vptr_point指向的都是同乙個**。*(pt1._vptr_point) == *(pt2._vptr_point),因為每個類的虛函式表只有乙份!

2)、_vptr_point只是為了說明問題而模擬出來的,也就是說c++程式是不能訪問這個東西的。(為了顯示我的博學,寫個詩句在這裡做驗證:不識廬山真面目,只緣身在此山中),所以你不能通過c++本身來理解c++底層的機制,只能通過更為底層的語言(彙編,或者機器碼,甚至可以用0-1電流來理解都是可以滴)

缺點就是上面二種型別的優點,這個優點就是上面兩種模型的缺點,就這麼簡單。

下面以point2d的記憶體布局圖結束這篇筆記吧。(_vptr_point2d所指向的這個**就是point2d的虛函式表,留意一下和上面的point pt的布局有什麼不同)

關於這個模型對c++的影響到底有多大,請看下文~

C 物件模型筆記 物件的三種記憶體布局

下面的c 定義了乙個類 point 現在定義 point 的乙個物件pt point pt 這個筆記討論的問題就是 如果讓你設計這個物件的記憶體布局,你會怎麼設計它?記憶體布局在這裡指的是物件各個成員在記憶體的排放 下面給出 lippman 在 深度探索 c 物件模型 中提出的三種可能的記憶體布局。...

C 三種記憶體物件比較

摘自 c 將記憶體劃分為三個邏輯區域 堆 棧和靜態儲存區。既然如此,我稱位於它們之中的物件分別為堆物件,棧物件以及靜態物件。三種記憶體物件的比較 棧物件的優勢是在適當的時候自動生成,又在適當的時候自動銷毀,不需要程式設計師操心 而且棧物件的建立速度一般較堆物件快,因為分配堆物件時,會呼叫operat...

C 物件模型和布局(三種經典類物件記憶體布局)

侯捷翻譯的 深度探索c 物件模型 一書中,對c 物件模型進行了三種典型模型劃分,分別為簡單物件模型 a object model 驅動物件模型 a table drive object model c 物件模型 the c object model 本文以及之後的部落格主要總結的都是最後乙個c 物件...