1. 程式人生 > >【轉載。侵刪】C++內存分配一些問題

【轉載。侵刪】C++內存分配一些問題

不能 沒有初始化 vc++ 諾依曼 為什麽 fun sed nbsp 動態申請

程序在的內存中的分配(常量,局部變量,全局變量,程序代碼)

一. 在c中分為這幾個存儲區
1.棧 - 由編譯器自動分配釋放
2.堆 - 一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收
3.全局區(靜態區),全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。- 程序結束釋放
4.另外還有一個專門放常量的地方。- 程序結束釋放

在函數體中定義的變量通常是在棧上,用malloc, calloc, realloc等分配內存的函數分配得到的就是在堆上。在所有函數體外定義的是全局量,加了static修飾符後不管在哪裏都存放在全局區(靜態區),在所有函數體外定義的static變量表示在該文件中有效,不能extern到別的文件用,在函數體內定義的static表示只在該函數體內有效。另外,函數中的"adgfdf"這樣的字符串存放在常量區。比如:

?
//main.cpp int a = 0; // 全局初始化區 char *p1; // 全局未初始化區 void main() { int b; // 棧區 char s[] = "abc"; // 棧區 char *p2; // 棧區 char *p3 = "123456"; // p3在棧區; "123456\0" 在常量區, static int c =0; // 全局(靜態)初始化區 p1 = (char *)malloc(10); p2 = (char *)malloc(20);
// 分配得來的10和20字節的區域就在堆區 strcpy(p1, "123456"); // "123456\0" 放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。 }

二.在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區
1.棧,
就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。裏面的變量通常是局部變量、函數參數等。
2.堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那麽在程序結束後,操作系統會自動回收。
3.自由存儲區,

就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
4.全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++裏面沒有這個區分了,他們共同占用同一塊內存區。
5.常量存儲區,這是一塊比較特殊的存儲區,他們裏面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改)

以上引自http://www.cnblogs.com/JCSU/articles/1051579.html

三、堆和棧的理論知識
申請方式
stack: 由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中為b開辟空間
heap: 需要程序員自己申請,並指明大小,在c中malloc函數
如p1 = (char *)malloc(10);
在C++中用new運算符
如p2 = (char *)malloc(10);

但是註意p1、p2本身是在棧中的。

三。

全局變量如果不初始化,則默認為0,編譯時編譯器不提示“變量未初始化”。

VC++ 6.0 編譯器編譯期存儲器分配模型 (內存布局)

分類: VC學習

VC ++ 6.0 編譯器編譯期存儲器分配模型(內存布局)

----轉載自網絡

一、內存區域的劃分

一個由C/C++編譯的程序占用的內存分為以下幾個部分:

1)、棧區(Stack):由編譯器(Compiler)自動分配釋放,存放函數的參數值,局部變的值等。其操作方式類似於數據結構中的棧。

2)、堆區(Heap ):一般由程序員分配釋放,若程序員不釋放,程序結束時可能由OS回收。註意它與數據結構中的堆是兩回事,分配

方式倒是類似於鏈表。

3)、全局區(靜態區)(static):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全

局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束後由系統釋放。

4)、文字常量區:常量字符串就是放在這裏的。程序結束後由系統釋放。

5)、程序代碼區:存放函數體的二進制代碼。

二、測試案例(源碼與反匯編對照)

2.1 測試案例源碼與反匯編對照

為了能夠形象地說明內存布局模型,先來看一段Win32 Console Application代碼(表3.1),其中,加粗文字(行最左端為行標號)

為C源代碼,未加粗文字(行最左端為地址)為反匯編後的指令代碼。看上去比較零亂,不過一定要耐住性子,後面的文字將基於此。

技術分享圖片

技術分享圖片

3.2 內存布局圖

對於該案例,以下幾幅圖形象地說明了第2節提到的內存5大區域。需要註意的是,圖中各區域的起始地址不是絕對的,不同的編譯環境可能不完全相同,這裏給出的只是一個典型示例。需要註意的是,不同區域地址編址方向也不同。

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

技術分享圖片

3、應用

通過對第3節案例的理解,我們將對一些現象予以解釋。

3.1、變量初始化

1)局部變量將被分配在棧中,如果不初始化,則為不可預料值。編譯時,編譯器將拋出一個編號為C4700警告錯誤(local variable ‘變量名‘ used without having been initialized)。

表4.1代碼測試了局部變量未初始化的情況。

技術分享圖片

該測試的一個典型的輸出結果為:-858993460,同時,編譯時編譯器拋出了一條警告錯誤。

2)全局變量如果不初始化,則默認為0,編譯時編譯器不提示“變量未初始化”。

表4.2代碼測試了全局變量未初始化的情況。

技術分享圖片

該測試的輸出結果為:0.

3)全局變量初始化為0與不初始化效果一樣。請留意表3.1第9行代碼,即

intinit_array_g1[10]={0}; //初始化的全局數組1

等效於:

int init_array_g1[10]; //初始化的全局數組1

當然,出於謹慎,我們還是建議在使用全局變量前對其初始化。

3.2 變量初始化對代碼空間的影響

本小節任然討論變量初始化問題,但出於重視,我們將其獨立成小節。現在看兩個測試案例。

案例1:建立Win32 Console Application工程,工程名:Test1,代碼如表4.3。

技術分享圖片

編譯成Debug版本,察看Debug目錄下的Test1.exe可執行文件大小,典型大小約184KB(約0.18MB)。

案例2:建立Win32 Console Application工程,工程名:Test2,代碼如表4.4。

技術分享圖片

編譯成Debug版本,察看Debug目錄下的Test2.exe可執行文件大小,典型大小約46MB。

兩個案例唯一區別不過在於是用0還是1初始化 init_array_g1[]數組第0個元素。生成的可執行文件大小卻天壤之別。

上面已經說過,對於全局變量初始化為0與不初始化效果一樣。因此,這裏的Test1案例並沒有對全局變量初始化。

那麽全局變量初始化於不初始化對代碼空間又有什麽影響呢?

我們知道,運行於基於馮·諾依曼體系結構系統上的程序,數據和程序是一起存儲了。因此,編譯時,編譯器會將全局變量的初始化數據捆綁到最終生成的程序文件中,而對於未初始化的全局變量只是為其分配(指示)了存儲位置,不會將大量的0捆綁到程序中。

現在再來看以上兩個案例。Test1實質上沒有初始化全局變量,編譯時編譯器只是為了init_array_g1[]指出了將要使用的內存位置,而不發生 數據綁定。Test2則不同,它將init_array_g1[0]初始化為1,其它元素全部初始化為0,因此,編譯器將把 init_array_g1[]數組的10000000個元素的初始化數據全部捆綁到最終的可執行文件中,導致編譯後的文件十分龐大。

3.3 關於堆和棧

由於歷史原因,我們習慣把堆和棧合在一起稱呼(堆棧),然而,在這裏我們要嚴格區分堆和棧的概念。

例程中聲明的局部變量被分配在棧中,而棧的大小是相當有限的(一、兩個兆),龐大的數組可能使棧不夠用,造成運行期棧溢出(Overflow)錯誤(註意:不是編譯器錯誤),而堆的大小主要取決於系統可用內存和虛存的多少。下面來看幾個例子:

案例3代碼如表4.5所示:

技術分享圖片

編譯該代碼,沒有編譯期錯誤。執行時卻發生了運行期錯誤(提示Statck Overflow),因為棧空間不夠用。

案例4,把案例3代碼改一下,數組定位為全局變量,如表4.6所示:

技術分享圖片

編譯該代碼,沒有編譯期錯誤,也不發生運行期錯誤。因為全局變量不是分配在棧中的(註意:也不在堆中),能用多大空間取決於系統可用內存和虛存的多少。

對於案例3的問題還有一種方法可以解決:動態申請內存空間。

動態申請的內存空間是在運行期分配的,一旦申請成功,將分配在堆中,因此,大小也是取決於系統可用內存和虛存的多少。

案例5:把案例3代碼用另一種方法改一下,如表4.7所示。

技術分享圖片

案例5的內存空間在堆中。還有一點不同於案例4:案例4的內存空間是在編譯器分配的,而案例5的內存空間是在運行期分配的,有可能分配不到空間。

3.4) 地址遞減編制方式

或許其它資料中已經描述了“地址遞減”編址方式分配內存的概念,所謂“地址遞減“是指編譯器編譯程序時,按變量聲明先後,從可分配內存中從高地址向低地址分配內存。什麽意思?還是先來看一個例子。

案例6是一個有邏輯錯誤的程序(表4.7所示),不妨稱其為“變態”程序。那麽它是如何BT的呢?

技術分享圖片

這個程序沒有編譯器錯誤,但卻是一個死循環程序。我們想知道的是:它為什麽是個死循環,而不是其它什麽錯誤?通過以上文字對內存布局的介紹,我們已經可以很容易解釋之。

仿照第3節內容可以畫出內存布局示意圖(如圖4.1所示,圖中起始地址只是一個典型情況)。

技術分享圖片

註意,程序中引用了array[10]————數組下標越界(VC++6.0編譯器可以檢查出顯示的下標越界,但是不檢查隱式的下標越界)。循環內部會將 所謂的array[10]置1,而從圖4.1可知,array[10]實質上就是i,導致程序最終死循環也就理所當然了。

一切變得明朗起來,我們不僅解釋了程序中的問題,同時還明白了“地址遞減”編址方式並不神秘,它原來就是我們前面提到的棧內存區的編址方式。

【轉載。侵刪】C++內存分配一些問題