【轉載。侵刪】C++內存分配一些問題
一. 在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.自由存儲區,
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學習 2011-04-02 21:00 194人閱讀 評論(0) 收藏 舉報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++內存分配一些問題