預備知識
在c++
中,記憶體分成
5個區,他們分別是堆、棧、自由儲存區、全域性
/靜態儲存區和常量儲存區。
棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。
堆,就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般乙個new就要對應乙個delete。如果程式設計師沒有釋放掉,那麼在程式結束後,作業系統會自動**。
自由儲存區,就是那些由malloc等分配的記憶體塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。[注]
堆和自由儲存區一般認為是同一名詞。或者,自由儲存區的一部分被開成堆,剩下的則是未使用。堆和棧都可以在自由儲存區里
進行自動擴充套件,直到2者相接而成為記憶體耗盡。
全域性/靜態儲存區,全域性變數和靜態變數被分配到同一塊記憶體中,在以前的c語言中,全域性變數又分為初始化的和未初始化的,在c++裡面沒有這個區分了,他們共同占用同一塊記憶體區。
常量儲存區,這是一塊比較特殊的儲存區,他們裡面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改,而且方法很多)[注]
常量儲存區則是「全域性/靜態儲存區」的一部分,在os支援常量段的情況下,編譯器可以把一部分const的全域性變數放在這個位置。而且,這個「全域性/靜態儲存區」並沒有被c、c++標準給出名稱,都是俗稱,各個資料上可以名字差異很大。
記憶體中,堆和棧的區別
主要的區別有以下幾點: 1
、管理方式不同; 2
、空間大小不同; 3
、能否產生碎片不同; 4
、生長方向不同; 5
、分配方式不同; 6
、分配效率不同;
管理方式不同
對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式設計師控制,容易產生memory leak。
空間大小不同
一般來講在32位系統下,堆記憶體可以達到4g的空間,從這個角度來看堆記憶體幾乎是沒有什麼限制的。但是對於棧來講,一般都是有一定的空間大小的
能否產生碎片不同
對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的碎片,使程式效率降低。對於棧來講,則不會存在這個問題,因為棧是先進後出的佇列,他們是如此的一一對應,以至於永遠都不可能有乙個記憶體塊從棧中間彈出,在他彈出之前,在他上面的後進的棧內容已經被彈出
生長方向不同
對於堆來講,生長方向是向上的,也就是向著記憶體位址增加的方向;對於棧來講,它的生長方向是向下的,是向著記憶體位址減小的方向增長。
分配方式不同
堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如區域性變數的分配。動態分配由alloca函式進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
分配效率不同
棧是機器系統提供的資料結構,計算機會在底層對棧提供支援:分配專門的暫存器存放棧的位址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是c/c++函式庫提供的,它的機制是很複雜的,例如為了分配一塊記憶體,庫函式會按照一定的演算法(具體的演算法可以參考資料結構/作業系統)在堆記憶體中搜尋可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由於記憶體碎片太多),就有可能呼叫系統功能去增加程式資料段的記憶體空間,這樣就有機會分到足夠大小的記憶體,然後進行返回。顯然,堆的效率比棧要低得多。
使用棧就像我們去飯館吃飯,直觀點菜(發出申請),付錢和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和稍微工作,他的好處就是快捷,但自由度小。
使用堆就像我們自己動手做喜歡吃的菜餚,比較麻煩,但能做出符合自己口味的菜,自由度大[注
]棧物件的優勢是在適當的時候自動生成,又在適當的時候自動銷毀,不需要程式設計師操心;而且
棧物件的建立速度一般較堆物件快,因為分配堆物件時,會呼叫
operator new
操作,operator new
會採用某種記憶體空間搜尋演算法,而該搜尋過程可能是很費時間的,產生棧物件則沒有這麼麻煩,它僅僅需要移動棧頂指標就可以了。但是要注意的是,通常棧
空間容量比較小,一般是
1mb~
2mb,所以體積比較大的物件不適合在棧中分配。特別要注意遞迴函式中最好不要使用棧物件,因為隨著遞迴呼叫深度的增加,
所需的棧空間也會線性增加,當所需棧空間不夠時,便會導致棧溢位,這樣就會產生執行時錯誤。
堆物件建立和銷毀都要由程式設計師負責,所以,如果處理不好,就會發生記憶體問題。如果分配了
堆物件,卻忘記了釋放,就會產生記憶體洩漏;而
如果已釋放了物件,卻沒有將相應的指標置為
null
,該指標就是所謂的「懸掛指標」,再度使用此指標時,就會出現非法訪問,嚴重時就導致程式崩潰。
但是高效
的使用堆物件也可以大大的提高**質量。比如,我們需要建立乙個大物件,且需要被多個函式所訪問,那麼這個時候建立乙個堆物件無疑是良好的選擇,因為我們
通過在各個函式之間傳遞這個堆物件的指標,便可以實現對該物件的共享,相比整個物件的傳遞,大大的降低了物件的拷貝時間。另外,相比於棧空間,堆的容量要
大得多。實際上,當物理記憶體不夠時,如果這時還需要生成新的堆物件,通常不會產生執行時錯誤,而是系統會使用虛擬記憶體來擴充套件實際的物理記憶體。
靜態儲存區。所有的靜態物件、全域性物件都於靜態儲存區分配。
關於全域性物件,是在
main()
函式執行前就分配好了的。
其實,在
main()
函式中的顯示代
碼執行之前,會呼叫乙個由編譯器生成的
_main()
函式,而
_main()
函式會進行所有全域性物件的的構造及初始化工作。而在
main()
函式結束之
前,會呼叫由編譯器生成的
exit
函式,來釋放所有的全域性物件。除了全域性靜態物件,還有區域性靜態物件
通和class
的靜態成員
,區域性靜態物件是在函式中
定義的,就像棧物件一樣,只不過,其前面多了個
static
關鍵字。區域性靜態物件的生命期是從其所在函式第一次被呼叫,更確切地說,是
當第一次執行到該靜
態物件的宣告**時,產生該靜態區域性物件,直到整個程式結束時,才銷毀該物件。
class
的靜態成員的生命週期是該
class
的第一次呼叫到程式的結束。
記憶體分配方式
記憶體分配方式有三種:
[1]從靜態儲存區域分配。
內存在程式編譯的時候就已經分配好,這塊內存在程式的整個執行期間都存在。
例如全域性變數,
static
變數。[2]
在棧上建立。在
執行函式
時,函式內
區域性變數
的儲存單元都可以在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於
處理器的指令集
中,效率很高,但是分配的記憶體容量有限。
[3]從堆上分配,亦稱動態記憶體分配。程式在執行的時候用
malloc
或new
申請任意多少的記憶體,程式設計師自己負責在何時用
free
或delete
釋放記憶體。
動態記憶體的生存期由程式設計師決定,使用非常靈活,但如果在堆上分配了空間,
就有責任**它
,否則執行的程式會出現記憶體洩漏,頻繁地分配和釋放不同大
小的堆空間將會產生堆內碎塊。
[4]文字常量分配在文字常量區,程式結束後由系統釋放。
應用例項:
這是乙個前輩寫的,非常詳細
int a = 0;
全域性初始化區
char *p1;
全域性未初始化區
main()
static
的內部機制:
靜態資料成員要在程式一開始執行時就必須存在。因為函式在程式執行中被呼叫,所以靜態資料成員不能在任何函式內分配空間和初始化。
這樣,它的空間分配有三個可能的地方,一是作為類的外部介面的標頭檔案,那裡有類宣告;二是類定義的內部實現,那裡有類的成員函式定義;三是應用程式的
main
()函式前的全域性資料宣告和定義處。
靜態資料成員要實際地分配空間,故不能在類的宣告中定義(只能宣告資料成員)。類宣告只宣告乙個類的
「尺寸和規格
」,並不進行實際的記憶體分配,所以在類宣告中寫成定義是錯誤的。它也不能在標頭檔案中類宣告的外部定義,因為那會造成在多個使用該類的原始檔中,對其重複定義。
static
被引入以告知編譯器,將變數儲存在程式的靜態儲存區而非棧上空間,靜態
資料成員按定義出現的先後順序依次初始化,注意靜態成員巢狀時,要保證所巢狀的成員已經初始化了。消除時的順序是初始化的反順序。
static
的優勢:
可以節省記憶體,因為它是所有物件所公有的,因此,對多個物件來說,靜態資料成員只儲存一處,供所有物件共用。靜態資料成員的值對每個物件都是一樣,但它的值是可以更新的。只要對靜態資料成員的值更新一次,保證所有物件訪問更新後的相同的值,這樣可以提高時間效率。
引用靜態資料成員時,採用如下格式:
<
類名》::<
靜態成員名
>
如果靜態資料成員的訪問許可權允許的話(即
public
的成員)
,可在程式中,按上述格式
『檔案過大,有興趣者可通過郵箱索要』
Java基礎 程式執行過程中的記憶體管理
例如我們寫的乙個程式,存放在硬碟的某個區域,如果不執行此程式,它就會默默的存放在那裡。當我們執行它時,它執行的過程如下 1.將程式load到記憶體區域 2.作業系統自己會找到程式的main方法,從main開始執行程式 3.當程式開始執行時,程式中的資料會被記憶體分類管理起來。基本有四類記憶體管理 h...
程式執行過程中的各個暫存器!
eax 是 累加器 accumulator 它是很多加法乘法指令的預設暫存器。ebx 是 基位址 base 暫存器,在記憶體定址時存放基位址。ecx 是計數器 counter 是重複 rep 字首指令和loop指令的內定計數器。edx 則總是被用來放整數除法產生的餘數。esi edi分別叫做 源 目...
程式執行的記憶體分析過程
1.將show方法存入方法區,構造person方法,初始化變數name age,構造方法移出棧 2.給main方法建立乙個棧,給name,age分別賦值,呼叫show方法,此時將物件的位址0x10傳遞給p1,p1.show 棧被移出 3.再次構造person方法,初始化變數name age,構造方法...