C++內(nèi)存邏輯區(qū)域劃分方式介紹
C++編程語言是一門比較高深的計(jì)算機(jī)應(yīng)用語言。它的很多功能都需要我們在不斷的學(xué)習(xí)與實(shí)踐過程中去探索。比如C++內(nèi)存相關(guān)內(nèi)容,就是其中一個(gè)重要的知識點(diǎn)。C++內(nèi)存邏輯區(qū)域總共被分為三種:堆、棧和靜態(tài)存儲區(qū)。我們稱位于它們之中的對象分別為堆對象,棧對象以及靜態(tài)對象。#t#
C++內(nèi)存邏輯區(qū)域之a(chǎn)棧:一般用于存放局部變量或?qū)ο螅缥覀冊诤瘮?shù)定義中用類似下面語句聲明的對象:
Type stack_object ;
stack_object便是一個(gè)棧對象,生命周期是從定義點(diǎn)開始,函數(shù)返回時(shí),結(jié)束。幾乎所有的臨時(shí)對象都是棧對象。比如,下面的函數(shù)定義:
Type fun(Type object) ;
這個(gè)函數(shù)至少產(chǎn)生兩個(gè)臨時(shí)對象,首先,參數(shù)是按值傳遞的,所以會調(diào)用拷貝構(gòu)造函數(shù)生成一個(gè)臨時(shí)對象object_copy1 ,在函數(shù)內(nèi)部使用的不是使用的不是object,而是object_copy1,自然,object_copy1是一個(gè)棧對象,它在函數(shù)返回時(shí)被釋放;還有這個(gè)函數(shù)是值返回的,在函數(shù)返回時(shí),如果我們不考慮返回值優(yōu)化(NRV),那么也會產(chǎn)生一個(gè)臨時(shí)對象object_copy2,這個(gè)臨時(shí)對象會在函數(shù)返回后一段時(shí)間內(nèi)被釋放。比如某個(gè)函數(shù)中有如下代碼:
Type tt ,result ; 生成兩個(gè)棧對象
tt = fun(tt) ; 函數(shù)返回時(shí),生成的是一個(gè)臨時(shí)對象object_copy2
上面的第二個(gè)語句的執(zhí)行情況是這樣的,首先函數(shù)fun返回時(shí)生成一個(gè)臨時(shí)對象object_copy2 ,然后再調(diào)用賦值運(yùn)算符執(zhí)行tt = object_copy2 ; 調(diào)用賦值運(yùn)算符 編譯器在我們毫無知覺的情況下,為我們生成了這么多臨時(shí)對象,而生成這些臨時(shí)對象的時(shí)間和空間的開銷可能是很大的,所以,對于“大”對象最好用const引用傳遞代替按值進(jìn)行函數(shù)參數(shù)傳遞了。
C++內(nèi)存邏輯區(qū)域之b 堆 又叫自由存儲區(qū),它是在程序執(zhí)行的過程中動態(tài)分配的,其最大的特性就是動態(tài)性。在C++中,所有堆對象的創(chuàng)建和銷毀都要由程序員負(fù)責(zé),如果處理不好,就會發(fā)生內(nèi)存問題。如分配了內(nèi)存而沒有釋放則造成內(nèi)存泄漏;如果已釋放了對象,卻沒有將相應(yīng)的指針置為NULL,則可能會造成“懸掛指針”或“野指針”,再度使用此指針時(shí),就會出現(xiàn)非法訪問,嚴(yán)重時(shí)就導(dǎo)致程序崩潰。
C++中一般通過new來為對象分配堆內(nèi)存空間(當(dāng)然,用malloc也可獲得C式堆內(nèi)存),并且返回指向該堆對象的指針。
C++內(nèi)存邏輯區(qū)域之c 靜態(tài)存儲區(qū) 所有的靜態(tài)對象、全局對象都于靜態(tài)存儲區(qū)分配。關(guān)于全局對象,是在main()函數(shù)執(zhí)行前就分配好了的。其實(shí),在main()函數(shù)中的顯示代碼執(zhí)行之前,會調(diào)用一個(gè)由編譯器生成的_main()函數(shù),而_main()函數(shù)會進(jìn)行所有全局對象的的構(gòu)造及初始化工作。而在main()函數(shù)結(jié)束之前,會調(diào)用由編譯器生成的exit函數(shù),來釋放所有的全局對象。比如下面的代碼:
- void main(void)
- {
- … … 顯式代碼
- }
實(shí)際上,被轉(zhuǎn)化成這樣:
- void main(void)
- {
- _main(); 隱式代碼,由編譯器產(chǎn)生,用以構(gòu)造所有全局對象
- … … 顯式代碼
- … …
- exit() ; 隱式代碼,由編譯器產(chǎn)生,用以釋放所有全局對象
- }
假設(shè)我們要在main()函數(shù)執(zhí)行之前做某些準(zhǔn)備工作,那么我們可以將這些準(zhǔn)備工作寫到一個(gè)自定義的全局對象的構(gòu)造函數(shù)中,這樣,在main()函數(shù)的顯式代碼執(zhí)行之前,這個(gè)全局對象的構(gòu)造函數(shù)會被調(diào)用,執(zhí)行預(yù)期的動作,這樣就達(dá)到了我們的目的。剛才講的是靜態(tài)存儲區(qū)中的全局對象,那么,也有對應(yīng)的局部靜態(tài)對象,局部靜態(tài)對象通常也是在函數(shù)中定義的,就像棧對象一樣,只不過,其前面多了個(gè)static關(guān)鍵字。局部靜態(tài)對象的生命期是從其所在函數(shù)第一次被調(diào)用,更確切地說,是當(dāng)?shù)谝淮螆?zhí)行到該靜態(tài)對象的聲明代碼時(shí),產(chǎn)生該靜態(tài)局部對象,直到整個(gè)程序結(jié)束時(shí),才銷毀該對象。還有一種靜態(tài)對象,那就是它作為class的靜態(tài)成員??紤]這種情況時(shí),就牽涉了一些較復(fù)雜的問題。
第一個(gè)問題是class的靜態(tài)成員對象的生命期,class的靜態(tài)成員對象隨著第一個(gè)class object的產(chǎn)生而產(chǎn)生,在整個(gè)程序結(jié)束時(shí)消亡。也就是有這樣的情況存在,在程序中我們定義了一個(gè)class,該類中有一個(gè)靜態(tài)對象作為成員,但是在程序執(zhí)行過程中,如果我們沒有創(chuàng)建任何一個(gè)該class object,那么也就不會產(chǎn)生該class所包含的那個(gè)靜態(tài)對象。還有,如果創(chuàng)建了多個(gè)class object,那么所有這些object都共享那個(gè)靜態(tài)對象成員。
第二個(gè)問題是,當(dāng)出現(xiàn)下列情況時(shí):
- class Base
- {
- public
- static Type s_object ;
- }
- class Derived1 public Base 公共繼承
- {
- … … other data
- }
- class Derived2 public Base 公共繼承
- {
- … … other data
- }
- Base example ;
- Derivde1 example1 ;
- Derivde2 example2 ;
- example.s_object = …… ;
- example1.s_object = …… ;
- example2.s_object = …… ;
請注意上面標(biāo)為黑體的三條語句,它們所訪問的s_object是同一個(gè)對象嗎?答案是肯定的, 我們知道,當(dāng)一個(gè)類比如Derived1,從另一個(gè)類比如Base繼承時(shí),那么,可以看作一個(gè)Derived1對象中含有一個(gè)Base型的對象,這就是一個(gè)subobject。當(dāng)我們將一個(gè)Derived1型的對象傳給一個(gè)接受非引用Base型參數(shù)的函數(shù)時(shí)會發(fā)生切割,那么是怎么切割的呢?相信現(xiàn)在你已經(jīng)知道了,那就是僅僅取出了Derived1型的對象中的subobject,而忽略了所有Derived1自定義的其它數(shù)據(jù)成員,然后將這個(gè)subobject傳遞給函數(shù)(實(shí)際上,函數(shù)中使用的是這個(gè)subobject的拷貝)。
所有繼承Base類的派生類的對象都含有一個(gè)Base型的subobject(這是能用Base型指針指向一個(gè)Derived1對象的關(guān)鍵所在,自然也是多態(tài)的關(guān)鍵了),而所有的subobject和所有Base型的對象都共用同一個(gè)s_object對象,從Base類派生的整個(gè)繼承體系中的類的實(shí)例都會共用同一個(gè)s_object對象了。
以上就是對C++內(nèi)存邏輯區(qū)域的相關(guān)介紹。