程式執行過程中的記憶體活動
預備知識
在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區。
棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。
|
堆,就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new就要對應一個delete。如果程式設計師沒有釋放掉,那麼在程式結束後,作業系統會自動回收。
|
自由儲存區,就是那些由malloc等分配的記憶體塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。[注] 堆和自由儲存區一般認為是同一名詞。 或者,自由儲存區的一部分被開成堆,剩下的則是未使用。堆和棧都可以在自由儲存區裡
|
全域性/靜態儲存區,全域性變數和靜態變數被分配到同一塊記憶體中,在以前的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] 文字常量分配在文字常量區,程式結束後由系統釋放。
應用例項:
這是一個前輩寫的,非常詳細
//main.cpp
int a = 0; 全域性初始化區
char *p1; 全域性未初始化區
main()
{
int b;// 棧
char s[] = "abc"; //棧
char *p2; //棧
char *p3 = "123456"; //"123456\0"在常量區,p3在棧上。
static int c =0; //全域性(靜態)初始化區
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
//分配得來得10和20位元組的區域就在堆區。
strcpy(p1, "123456"); //123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。}
static的內部機制:
靜態資料成員要在程式一開始執行時就必須存在。因為函式在程式執行中被呼叫,所以靜態資料成員不能在任何函式內分配空間和初始化。
這樣,它的空間分配有三個可能的地方,一是作為類的外部介面的標頭檔案,那裡有類宣告;二是類定義的內部實現,那裡有類的成員函式定義;三是應用程式的main()函式前的全域性資料宣告和定義處。
靜態資料成員要實際地分配空間,故不能在類的宣告中定義(只能宣告資料成員)。類宣告只宣告一個類的“尺寸和規格”,並不進行實際的記憶體分配,所以在類宣告中寫成定義是錯誤的。它也不能在標頭檔案中類宣告的外部定義,因為那會造成在多個使用該類的原始檔中,對其重複定義。
static被引入以告知編譯器,將變數儲存在程式的靜態儲存區而非棧上空間,靜態
資料成員按定義出現的先後順序依次初始化,注意靜態成員巢狀時,要保證所巢狀的成員已經初始化了。消除時的順序是初始化的反順序。
static的優勢:
可以節省記憶體,因為它是所有物件所公有的,因此,對多個物件來說,靜態資料成員只儲存一處,供所有物件共用。靜態資料成員的值對每個物件都是一樣,但它的值是可以更新的。只要對靜態資料成員的值更新一次,保證所有物件存取更新後的相同的值,這樣可以提高時間效率。
<類名>::<靜態成員名>
如果靜態資料成員的訪問許可權允許的話(即public的成員),可在程式中,按上述格式
『檔案過大,有興趣者可通過郵箱索要』
相關文章
- Java 執行過程中的記憶體模型Java記憶體模型
- 程式執行過程記憶體分析詳解記憶體
- 通過 HelloWorld 瞭解 Java 程式執行過程以及執行時記憶體Java記憶體
- 請教一個程式執行時,從磁碟到記憶體到CPU的過程?記憶體
- ART執行時為新建立物件分配記憶體的過程分析物件記憶體
- Java中記憶體中的Heap、Stack與程式執行的關係Java記憶體
- Linux上執行記憶體中的指令碼和程式Linux記憶體指令碼
- Java程式執行記憶體機制Java記憶體
- 記憶體池、程式池、執行緒池記憶體執行緒
- Java 程式執行過程Java
- 記憶體訪問全過程記憶體
- 程式執行時的記憶體空間分佈記憶體
- Javascript中new的執行過程JavaScript
- C程式執行時記憶體結構分析C程式記憶體
- JVM基本結構、類載入過程以及執行時記憶體溢位分析JVM記憶體溢位
- 程式語言執行過程
- 深入理解Java的堆記憶體和執行緒記憶體Java記憶體執行緒
- 淺析Java程式的執行過程Java
- Java 執行緒記憶體模型Java執行緒記憶體模型
- 設定SQLserver執行記憶體SQLServer記憶體
- Java 執行時的記憶體劃分Java記憶體
- KVC中setValue:forKey:的執行過程
- ORDER BY 在oracle中執行的大概過程Oracle
- 程式碼精簡執行過程
- 窺探JVM記憶體分配和回收的過程JVM記憶體
- 記一次"記憶體洩露"排查過程記憶體洩露
- 在記憶體中裝載並執行程式或程式集記憶體行程
- 指令的執行過程
- 圖解JVM記憶體模型及JAVA程式執行原理圖解JVM記憶體模型Java
- win10執行記憶體多大才夠用?windows10需要多大執行記憶體Win10記憶體Windows
- PHP 獲取程式碼執行時間和消耗的記憶體PHP記憶體
- Java多執行緒記憶體模型Java執行緒記憶體模型
- oracle儲存過程(procedure)中執行動態SQL小記Oracle儲存過程SQL
- Java應用程式中的記憶體洩漏及記憶體管理Java記憶體
- 剖析記憶體中的程式之祕記憶體
- 記一次使用windbg排查記憶體洩漏的過程記憶體
- 分析oc物件的記憶體結構及其建立過程物件記憶體
- Java 記憶體洩露的理解與解決過程Java記憶體洩露