記憶體物件筆記

2021-05-27 00:32:54 字數 4063 閱讀 5740

原文:

c++將記憶體劃分為三個邏輯區域:堆、棧和靜態儲存區。位於它們之中的物件分別為堆物件,棧物件以及靜態物件。

棧·棧,一般用於存放區域性變數或物件。它的生命期是從定義點開始,當所在函式返回時,生命結束。幾乎所

有的臨時物件都是棧物件。

·棧物件是在適當的時候建立,然後在適當的時候自動釋放(第一,在其生命期結束的時候自動釋放; 第

二,在其所在的函式發生異常的時候自動釋放。)

·棧物件,自動釋放時,會呼叫它自己的析構函式。

·type fun(type object);

這個函式至少產生兩個臨時物件:

- 引數是按值傳遞的,所以會呼叫拷貝建構函式生成乙個臨時物件;

- 還有這個函式是值返回的。不考慮返回值優化(nrv),那麼也會產生乙個臨時物件。 

·生成某些臨時物件的時間和空間的開銷可能是很大的,對於「大」物件最好用 const 引用傳遞代替按值

進行函式引數傳遞。

堆·堆,又叫自由儲存區,它是在程式執行的過程中動態分配的,最大的特性就是動態性。

·堆物件的建立和銷毀都要由程式設計師負責。

·如果分配了堆物件,卻忘記了釋放,就會產生記憶體洩漏;

如果已釋放了物件,卻沒有將相應的指標置為 null ,該指標就是所謂的「懸掛指標」,再度使用此指標

時,就會出現非法訪問,嚴重時就導致程式崩潰。

靜態儲存區

·所有的靜態物件、全域性物件都於靜態儲存區分配。

·全域性物件,是在 main() 函式執行前就分配好了的。

·其實,在 main() 函式中的顯示**執行之前,會呼叫乙個由編譯器生成的 _main() 函式,而 _main()

函式會進行所有全域性物件的的構造及初始化工作。而在 main() 函式結束之前,會呼叫由編譯器生成的

exit 函式,來釋放所有的全域性物件。比如下面的**:

void main(void)

實際上,被轉化成這樣:

void main(void)

知道了這個之後,便可以由此引出一些技巧,如,假設我們要在 main() 函式執行之前做某些準備工作,

那麼我們可以將這些準備工作寫到乙個自定義的全域性物件的建構函式中,這樣,在 main() 函式的顯式代

碼執行之前,這個全域性物件的建構函式會被呼叫,執行預期的動作,這樣就達到了我們的目的。

·區域性靜態物件通常也是在函式中定義的,它的生命期是從第一次執行到該靜態物件的宣告**時,產生該

靜態區域性物件,直到整個程式結束時,才銷毀該物件。

·還有一種靜態物件,那就是它作為 class 的靜態成員。考慮這種情況時,就牽涉了一些較複雜的問題。

- 第乙個問題是class的靜態成員物件的生命期,class 的靜態成員物件隨著第乙個 class object 的產

生而產生,在整個程式結束時消亡。也就是有這樣的情況存在,在程式中我們定義了乙個 class ,該

類中有乙個靜態物件作為成員,但在程式執行過程中,如果我們沒有建立任何乙個該 class object ,

那麼也就不會產生該 class 所包含的那個靜態物件。還有,如果建立了多個 class object ,那麼所

有這些 object 都共享那個靜態物件成員。

- 第二個問題是,當出現下列情況時:

class base

class derived1 : public base / / 公共繼承

class derived2 : public base / / 公共繼承

base example ;

derivde1 example1 ;

derivde2 example2 ;

example.s_object = …… ;

example1.s_object = …… ;

example2.s_object = …… ;

請注意上面標為黑體的三條語句,它們所訪問的 s_object 是同乙個物件嗎?答案是肯定的,它們的確

是指向同乙個物件。當乙個模擬如 derived1 ,從另乙個模擬如 base 繼承時,那麼,可以看作乙個

derived1 物件中含有乙個 base 型的物件,這就是乙個 subobject。乙個 derived1 物件的大致記憶體

布局如下:

乙個 base 型的 subobject

dervied1 的其他成員

乙個 dervied1 物件

當我們將乙個 derived1 型的物件傳給乙個接受非引用 base 型引數的函式時會發生切割,即僅僅取出

了 derived1 型的物件中的 subobject ,而忽略了所有 derived1 自定義的其它資料成員,然後將這

個 subobject 傳遞給函式(實際上,函式中使用的是這個 subobject 的拷貝)。

所有繼承 base 類的派生類的物件都含有乙個 base 型的 subobject(這是能用 base 型指標指向乙個

derived1 物件的關鍵所在,自然也是多型的關鍵了),而所有的 subobject 和所有 base 型的物件都

共用同乙個 s_object 物件,自然,從 base 類派生的整個繼承體系中的類的例項都會共用同乙個

s_object 物件了。

三種記憶體物件的比較

·棧物件的優勢是在適當的時候自動生成,又在適當的時候自動銷毀,不需要程式設計師操心;而且棧物件的創

建速度一般較堆物件快,因為分配堆物件時,會呼叫 operator new 操作,operator new 會採用某種內

存空間搜尋演算法,而該搜尋過程可能是很費時間的,產生棧物件則沒有這麼麻煩,它僅僅需要移動棧頂指

針就可以了。但是要注意的是,通常棧空間容量比較小,一般是 1mb~2mb ,所以體積比較大的物件不適

合在棧中分配。特別要注意遞迴函式中最好不要使用棧物件,因為隨著遞迴呼叫深度的增加,所需的棧空

間也會線性增加,當所需棧空間不夠時,便會導致棧溢位,這樣就會產生執行時錯誤。

·堆物件。比如,我們需要建立乙個物件,能夠被多個函式所訪問,但是又不想使其成為全域性的,那麼這個

時候建立乙個堆物件無疑是良好的選擇,然後在各個函式之間傳遞這個堆物件的指標,便可以實現對該對

象的共享。另外,相比於棧空間,堆的容量要大得多。實際上,當物理記憶體不夠時,如果這時還需要生成

新的堆物件,通常不會產生執行時錯誤,而是系統會使用虛擬記憶體來擴充套件實際的物理記憶體。

·靜態物件。

- 首先是全域性物件,全域性物件為類間通訊和函式間通訊提供了一種最簡單的方式,雖然這種方式並不優

雅。一般而言,在完全的物件導向語言中,是不存在全域性物件的,比如 c#,因為全域性物件意味著不安

全和高耦合,在程式中過多地使用全域性物件將大大降低程式的健壯性、穩定性、可維護性和可復用性。

c++ 也完全可以剔除全域性物件,但是最終沒有,我想原因之一是為了相容 c。

- 其次是類的靜態成員,上面已經提到,基類及其派生類的所有物件都共享這個靜態成員物件,所以當需

要在這些 class 之間或這些 class objects 之間進行資料共享或通訊時,這樣的靜態成員無疑是很好

的選擇。

- 接著是靜態區域性物件,主要可用於儲存該物件所在函式被屢次呼叫期間的中間狀態,其中乙個最顯著的

例子就是遞迴函式,如果在遞迴函式中定義乙個 nonstatic 區域性物件,那麼當遞迴次數相當大時,所

產生的開銷也是巨大的。因為 nonstatic 區域性物件是棧物件,每遞迴呼叫一次,就會產生乙個這樣的

物件,每返回一次,就會釋放這個物件,而且,這樣的物件只侷限於當前呼叫層,對於更深入的巢狀層

和更淺露的外層,都是不可見的。每個層都有自己的區域性物件和引數。

在遞迴函式設計中,可以使用 static 物件替代 nonstatic 區域性物件(即棧物件),這不僅可以減少

每次遞迴呼叫和返回時產生和釋放 nonstatic 物件的開銷,而且 static 物件還可以儲存遞迴呼叫的

中間狀態,並且可為各個呼叫層所訪問。

·某塊記憶體中的資料是不變的,而型別就是我們戴上的眼鏡,當我們戴上一種眼鏡後,我們就會用對應的類

型來解釋記憶體中的資料,這樣不同的解釋就得到了不同的資訊。

所謂強制型別轉換實際上就是換上另一副眼鏡後再來看同樣的那塊記憶體資料。

另外要提醒的是,不同的編譯器對物件的成員資料的布局安排可能是不一樣的。

物件的記憶體

當 傳送訊息alloc 給類時,ios平台會在記憶體中分配記憶體塊。該記憶體塊包含了此類宣告的屬性的記憶體。下面讓我們看個例子 下面 宣告了乙個car類。定義了三個屬性 year,make,model。import inte ce car nsobject end 下面 建立了car 類和nsobj...

java物件記憶體

堆區 1.儲存的全部是物件,每個物件都包含乙個與之對應的class的資訊。class的目的是得到操作指令 2.jvm只有乙個堆區 heap 被所有執行緒共享,堆中不存放基本型別和物件引用,只存放物件本身。棧區 1.每個執行緒包含乙個棧區,棧中只儲存基礎資料型別的物件和自定義物件的引用 不是物件 物件...

物件記憶體分析

定義乙個類,用來模擬 手機 事物。屬性 品牌,顏色 行為 打 發簡訊 對應到類中 成員變數 屬性 string brand 品牌 double price string color 顏色 成員變數 行為 public void call string who 打 public void sendme...