代码区,初始化全局数据区,BSS,堆区,栈区,程序环境变量区简介
相關鏈接:
1、http://blog.csdn.net/wudebao5220150/article/details/12947445
2、http://www.360doc.com/content/12/0811/17/8185406_229615633.shtml
3、http://www.tuicool.com/articles/JJFZjq
進 程(執行的程序)會占用一定數量的內存,它或是用來存放從磁盤載入的程序代碼,或是存放取自用戶輸入的數據等等。不過進程對這些內存的管理方式因內存用途不一而不盡相同,有些內存是事先靜態分配和統一回收的,而有些卻是按需要動態分配和回收的。對任何一個普通進程來講,它都會涉及到5種不同的數據段(如代碼段,數據段,BSS段,堆段,棧段)。在進程被載入內存中時,基本上被分裂成主要的6個小的節(section)---如, .text節, .data節, .bss節, 堆節, 棧節, 環境/參數節.
一、Linux進程的五個段
下面我們來簡單歸納一下進程對應的內存空間中所包含的5種不同的數據區都是干什么的。
?
重點:
代碼段、數據段、堆棧段,這是一個概念
堆、棧、全局區、常量區,這是另一個概念
?
?
1)代碼段:代碼段是用來存放可執行文件的操作指令,也就是說是它是可執行程序在內存中的鏡像。代碼段需要防止在運行時被非法修改,所以只準許讀取操作,而不允許寫入(修改)操作——它是不可寫的。代碼段(code segment/text segment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,并且內存區域通常屬于只讀, 某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。
?
2)數據段:數據段用來存放可執行文件中已初始化全局變量,換句話說就是存放程序靜態分配的變量和全局變量。
?
3)BSS段:BSS段包含了程序中未初始化的全局變量,在內存中 bss段全部置零。BSS段(bss segment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬于靜態內存分配。
?
4)堆(heap):堆是用于存放進程運行中被動態分配的內存段,它的大小并不固定,可動態擴張或縮減。當進程調用malloc等函數分配內存時,新分配的內存就被動態添加到堆上(堆被擴張);當利用free等函數釋放內存時,被釋放的內存從堆中被剔除(堆被縮減)
它的物理內存空間是由程序申請的,并由程序負責釋放。
?
5)棧:棧又稱堆棧,棧是用戶存放程序臨時創建的局部變量,也就是說我們函數括弧“{}”中定義的變量(但不包括static聲明的變量,static意味著在數據段中存放變量)。除此以外,在函數被調用時,其參數也會被壓入發起調用的進程棧中,并且待到調用結束后,函數的返回值也會被存放回棧中。由于棧的先進先出特點,所以棧特別方便用來保存/恢復調用現場。從這個意義上講,我們可以把堆棧看成一個寄存、交換臨時數據的內存區。
? ?它是由操作系統分配的,內存的申請與回收都由OS管理。
?
舉個具體的C語言的例子吧:
//main.c
int a = 0; //全局初始化區
char *p1; //全局未初始化區
main()
{
static int c =0; //全局(靜態)初始化區
int b; //棧
char s[] = "abc"; //棧
char *p2; //棧
char *p3 = "123456"; //"123456\0"在常量區,p3在棧上。
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得來得10和20字節的區域就在堆區。
}
?
?
二、各個段在內存中的組織
各個段段在線性空間中的組織。直接上圖:
+-------------------------------- ? 高地址
+ envstrings 環境變量字串 ? ?
+--------------------------------
+ argv string 命令行字串 ? ? ? ?
+--------------------------------
?
+ env pointers 環境變量指針表
+--------------------------------
+ argv pointers命令行參數指針表
+--------------------------------
+ argc 命令行參數個數
+--------------------------------
?
+ ? ? main函數的棧幀 ?
+--------------------------------
+ ? ? 被調用函數的棧幀
+--------------------------------
+ ? ? ? ? ...... ? ? ? ? ? ? ? ?
+--------------------------------
+ ? ? ? 堆(heap) ? ? ? ? ? ? ? ?
+--------------------------------
+ BSS 未初始化全局數據 ? ??
+--------------------------------
+ ? Data 初始化的全局數據 ? ?
+--------------------------------
?
+ ? Text 代碼段 ? ? ? ? ? ? ? ??
+-------------------------------- ??
其中,Heap,BSS,Data這三個段在物理內存中是連續存放的,可以這么理解:這三個是一體的。Text、Stack是獨立存放的,這是現在Linux中個段的分布,在0.11中代碼段和數據段不是分立的,是在一起的也就是說數據段和代碼段是一個段,當然了,堆與BSS也與它們一起了。從0.11的task_struct中還可以看出數據段、堆棧段的描述符是一個,都在ldt[2]處。
?
上圖是進程的虛擬地址空間示意圖。
堆棧段:
1. 為函數內部的局部變量提供存儲空間。
2. 進行函數調用時,存儲“過程活動記錄”。
3. 用作暫時存儲區。如計算一個很長的算術表達式時,可以將部分計算結果壓入堆棧。
數據段(靜態存儲區):
包括BSS段的數據段,BSS段存儲未初始化的全局變量、靜態變量。數據段存儲經過初始化的全局和靜態變量。
代碼段:
又稱為文本段。存儲可執行文件的指令。
堆:
就像堆棧段能夠根據需要自動增長一樣,數據段也有一個對象,用于完成這項工作,這就是堆(heap)。堆區域用來動態分配的存儲,也就是用 malloc 函數活的的內存。calloc和realloc和malloc類似。前者返回指針的之前把分配好的內存內容都清空為零。后者改變一個指針所指向的內存塊的大小,可以擴大和縮小,他經常把內存拷貝到別的地方然后將新地址返回。
?
?
?
代碼段、數據段、堆棧段,這是一個概念
堆、棧、全局區、常量區,這是另一個概念
?
1、棧區(stack):由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。?
2、堆區(heap):由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表。?
3、全局區(靜態區):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域, 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 程序結束后由系統釋放。?
4、文字常量區:常量字符串就是放在這里的。 程序結束后由系統釋放。?
5、程序代碼區:存放函數體的二進制代碼。
?
?
?
在進程被載入內存中時,基本上被分裂成許多小的節(section)。我們比較關注的是6個主要的節:
(1) .text 節
? ? .text 節基本上相當于二進制可執行文件的.text部分,它包含了完成程序任務的機器指令。該節標記為只讀,如果發生寫操作,會造成segmentation fault。在進程最初被加載到內存中開始,該節的大小就被固定。
(2).data 節
? ? ? .data節用來存儲初始化過的變量,如:int a =0 ; 該節的大小在運行時固定的。
(3).bss 節
? ? 棧下節(belowstack section ,即.bss)用來存儲為初始化的變量,如:int a; 該節的大小在運行時固定的。
(4) 堆節
? ? 堆節(heapsection)用來存儲動態分配的變量,位置從內存的低地址向高地址增長。內存的分配和釋放通過malloc() 和 free() 函數控制。
(5) 棧節
? ? 棧節(stacksection)用來跟蹤函數調用(可能是遞歸的),在大多數系統上從內存的高地址向低地址增長。
同時,棧這種增長方式,導致了緩沖區溢出的可能性。
(6)環境/參數節
? ?環境/參數節(environment/argumentssection)用來存儲系統環境變量的一份復制文件,進程在運行時可能需要。例如,運行中的進程,可以通過環境變量來訪問路徑、shell 名稱、主機名等信息。該節是可寫的,因此在格式串(format string)和緩沖區溢出(buffer overflow)攻擊中都可以使用該節。
另外,命令行參數也保持在該區域中。
另外一個例子:
//main.cpp ?
int a = 0; ? ?//a在全局已初始化數據區 ?
char *p1; ? ?//p1在BSS區(未初始化全局變量) ?
main() ?
{?
int b; ? ?//b在棧區?
char s[] = "abc"; //s為數組變量,存儲在棧區,?
//"abc"為字符串常量,存儲在已初始化數據區?
char *p1,p2; ?//p1、p2在棧區?
char *p3 = "123456"; //123456\0在已初始化數據區,p3在棧區 ?
static int c =0; ?//C為全局(靜態)數據,存在于已初始化數據區?
//另外,靜態數據會自動初始化?
p1 = (char *)malloc(10);//分配得來的10個字節的區域在堆區?
p2 = (char *)malloc(20);//分配得來的20個字節的區域在堆區?
free(p1);?
free(p2);?
}
另一個牛人寫的windows上的相關:
數據區,代碼區,堆棧區,操作系統堆棧
堆和棧的區別
一、預備知識—程序的內存分配
一個由c/C++編譯的程序占用的內存分為以下幾個部分
1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧(一棧)。
2、堆區(heap) — 一般由程序員分配釋放(malloc等), 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事(兩堆),分配方式倒是類似于鏈表,呵呵。
3、全局區(靜態區):全局變量、(static)—,
4、文字常量區—常量字符串(abc ?123)就是放在這里的。 程序結束后由系統釋放
5、程序代碼區—存放函數體的二進制代碼。
二、例子程序?
這是一個前輩寫的,非常詳細?
//
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"優化成一個地方。?
}?
二、堆和棧的理論知識?
2.1申請方式?
stack: ()棧
由系統自動分配。 例如,聲明在函數中一個局部變量 int b; 系統自動在棧中為b開辟空間?
heap: ? 堆
需要程序員自己申請,并指明大小,在c中malloc函數?
如p1 = (char *)malloc(10);?
在C++中用new運算符?
如p2 = (char *)malloc(10);?
但是注意p1、p2本身是在棧中的。?
2.2?
申請后系統的響應?
棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。?
堆:首先應該知道操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,?
會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語句才能正確的釋放本內存空間。另外,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。?
2.3申請大小的限制?
棧:在Windows下,棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。?
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。?
2.4申請效率的比較:?
棧由系統自動分配,速度較快。但程序員是無法控制的。?
堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便.?
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。?
2.5堆和棧中的存儲內容?
棧: 在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的。?
當本次函數調用結束后,局部變量先出棧,然后是參數,(棧方式:先入后出(壓棧))最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。?
堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。?
2.6存取效率的比較?
char s1[] = "aaaaaaaaaaaaaaa";?
char *s2 = "bbbbbbbbbbbbbbbbb";?
aaaaaaaaaaa是在運行時刻賦值的;?
而bbbbbbbbbbb是在編譯時就確定的;?
但是,在以后的存取中,在棧上的數組比指針所指向的字符串(例如堆)快。?
比如:?
#include?
void main()?
{?
char a = 1;?
char c[] = "1234567890";?
char *p ="1234567890";?
a = c[1];?
a = p[1];?
return;?
}?
對應的匯編代碼?
10: a = c[1];?
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]?
0040106A 88 4D FC mov byte ptr [ebp-4],cl?
11: a = p[1];?
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]?
00401070 8A 42 01 mov al,byte ptr [edx+1]?
00401073 88 45 FC mov byte ptr [ebp-4],al?
第一種在讀取時直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據edx讀取字符,顯然慢了。?
2.7小結:?
堆和棧的區別可以用如下的比喻來看出:?
使用棧就象我們去飯館里吃飯,只管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。?
使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。?
windows進程中的內存結構
在閱讀本文之前,如果你連堆棧是什么多不知道的話,請先閱讀文章后面的基礎知識。?
接觸過編程的人都知道,高級語言都能通過變量名來訪問內存中的數據。那么這些變量在內存中是如何存放的呢?程序又是如何使用這些變量的呢?下面就會對此進行深入的討論。下文中的C語言代碼如沒有特別聲明,默認都使用VC編譯的release版。?
首先,來了解一下 C 語言的變量是如何在內存分部的。C 語言有全局變量(Global)、本地變量(Local),靜態變量(Static)、寄存器變量(Regeister)。每種變量都有不同的分配方式。先來看下面這段代碼:?
#include <stdio.h>?
int g1=0, g2=0, g3=0;?
int main()?
{?
static int s1=0, s2=0, s3=0;?
int v1=0, v2=0, v3=0;?
//打印出各個變量的內存地址?
printf("0x%08x\n",&v1); //打印各本地變量的內存地址?
printf("0x%08x\n",&v2);?
printf("0x%08x\n\n",&v3);?
printf("0x%08x\n",&g1); //打印各全局變量的內存地址?
printf("0x%08x\n",&g2);?
printf("0x%08x\n\n",&g3);?
printf("0x%08x\n",&s1); //打印各靜態變量的內存地址?
printf("0x%08x\n",&s2);?
printf("0x%08x\n\n",&s3);?
return 0;?
}?
編譯后的執行結果是:?
0x0012ff78?
0x0012ff7c?
0x0012ff80?
0x004068d0?
0x004068d4?
0x004068d8?
0x004068dc?
0x004068e0?
0x004068e4?
輸出的結果就是變量的內存地址。其中v1,v2,v3是本地變量,g1,g2,g3是全局變量,s1,s2,s3是靜態變量。你可以看到這些變量在內存是連續分布的,但是本地變量和全局變量分配的內存地址差了十萬八千里,而全局變量和靜態變量分配的內存是連續的。這是因為本地變量和全局/靜態變量是分配在不同類型的內存區域中的結果。對于一個進程的內存空間而言,可以在邏輯上分成3個部份:代碼區,靜態數據區和動態數據區。動態數據區一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動態數據區,棧是一種線性結構,堆是一種鏈式結構。進程的每個線程都有私有的“棧”,所以每個線程雖然代碼一樣,但本地變量的數據都是互不干擾。一個堆棧可以通過“基地址”和“棧頂”地址來描述。全局變量和靜態變量分配在靜態數據區,本地變量分配在動態數據區,即堆棧中。程序通過堆棧的基地址和偏移量來訪問本地變量。?
├———————┤低端內存區域?
│ …… │?
├———————┤?
│ 動態數據區 │?
├———————┤?
│ …… │?
├———————┤?
│ 代碼區 │?
├———————┤?
│ 靜態數據區 │?
├———————┤?
│ …… │?
├———————┤高端內存區域?
堆棧是一個先進后出的數據結構,棧頂地址總是小于等于棧的基地址。我們可以先了解一下函數調用的過程,以便對堆棧在程序中的作用有更深入的了解。不同的語言有不同的函數調用規定,這些因素有參數的壓入規則和堆棧的平衡。windows API的調用規則和ANSI C的函數調用規則是不一樣的,前者由被調函數調整堆棧,后者由調用者調整堆棧。兩者通過“__stdcall”和“__cdecl”前綴區分。先看下面這段代碼:?
#include <stdio.h>?
void __stdcall func(int param1,int param2,int param3)?
{?
int var1=param1;?
int var2=param2;?
int var3=param3;?
printf("0x%08x\n",¶m1); //打印出各個變量的內存地址?
printf("0x%08x\n",¶m2);?
printf("0x%08x\n\n",¶m3);?
printf("0x%08x\n",&var1);?
printf("0x%08x\n",&var2);?
printf("0x%08x\n\n",&var3);?
return;?
}?
int main()?
{?
func(1,2,3);?
return 0;?
}?
編譯后的執行結果是:?
0x0012ff78?
0x0012ff7c?
0x0012ff80?
0x0012ff70?
0x0012ff6c?
0x0012ff68?
├———————┤<—函數執行時的棧頂(ESP)、低端內存區域?
│ …… │?
├———————┤?
│ var 2 │?
├———————┤?
│ var 2 │?
├———————┤?
│ var 1 │?
├———————┤?
│ RET │?
├———————┤<—“__cdecl”函數返回后的棧頂(ESP)?
│ parameter 1 │?
├———————┤?
│ parameter 2 │?
├———————┤?
│ parameter 3 │?
├———————┤<—“__stdcall”函數返回后的棧頂(ESP)?
│ …… │?
├———————┤<—棧底(基地址 EBP)、高端內存區域?
上圖就是函數調用過程中堆棧的樣子了。首先,三個參數以從又到左的次序壓入堆棧,先壓“param3”,再壓“param2”,最后壓入“param1”;然后壓入函數的返回地址(RET),接著跳轉到函數地址接著執行(這里要補充一點,介紹UNIX下的緩沖溢出原理的文章中都提到在壓入RET后,繼續壓入當前EBP,然后用當前ESP代替EBP。然而,有一篇介紹windows下函數調用的文章中說,在windows下的函數調用也有這一步驟,但根據我的實際調試,并未發現這一步,這還可以從param3和var1之間只有4字節的間隙這點看出來);第三步,將棧頂(ESP)減去一個數,為本地變量分配內存空間,上例中是減去12字節(ESP=ESP-3*4,每個int變量占用4個字節);接著就初始化本地變量的內存空間。由于“__stdcall”調用由被調函數調整堆棧,所以在函數返回前要恢復堆棧,先回收本地變量占用的內存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前壓入參數占用的內存(ESP=ESP+3*4),繼續執行調用者的代碼。參見下列匯編代碼:?
;--------------func 函數的匯編代碼-------------------?
:00401000 83EC0C sub esp, 0000000C //創建本地變量的內存空間?
:00401003 8B442410 mov eax, dword ptr [esp+10]?
:00401007 8B4C2414 mov ecx, dword ptr [esp+14]?
:0040100B 8B542418 mov edx, dword ptr [esp+18]?
:0040100F 89442400 mov dword ptr [esp], eax?
:00401013 8D442410 lea eax, dword ptr [esp+10]?
:00401017 894C2404 mov dword ptr [esp+04], ecx?
……………………(省略若干代碼)?
:00401075 83C43C add esp, 0000003C ;恢復堆棧,回收本地變量的內存空間?
:00401078 C3 ret 000C ;函數返回,恢復參數占用的內存空間?
;如果是“__cdecl”的話,這里是“ret”,堆棧將由調用者恢復?
;-------------------函數結束-------------------------?
;--------------主程序調用func函數的代碼--------------?
:00401080 6A03 push 00000003 //壓入參數param3?
:00401082 6A02 push 00000002 //壓入參數param2?
:00401084 6A01 push 00000001 //壓入參數param1?
:00401086 E875FFFFFF call 00401000 //調用func函數?
;如果是“__cdecl”的話,將在這里恢復堆棧,“add esp, 0000000C”?
聰明的讀者看到這里,差不多就明白緩沖溢出的原理了。先來看下面的代碼:?
#include <stdio.h>?
#include <string.h>?
void __stdcall func()?
{?
char lpBuff[8]="\0";?
strcat(lpBuff,"AAAAAAAAAAA");?
return;?
}?
int main()?
{?
func();?
return 0;?
}?
編譯后執行一下回怎么樣?哈,“"0x00414141"指令引用的"0x00000000"內存。該內存不能為"read"。”,“非法操作”嘍!"41"就是"A"的16進制的ASCII碼了,那明顯就是strcat這句出的問題了。"lpBuff"的大小只有8字節,算進結尾的\0,那strcat最多只能寫入7個"A",但程序實際寫入了11個"A"外加1個\0。再來看看上面那幅圖,多出來的4個字節正好覆蓋了RET的所在的內存空間,導致函數返回到一個錯誤的內存地址,執行了錯誤的指令。如果能精心構造這個字符串,使它分成三部分,前一部份僅僅是填充的無意義數據以達到溢出的目的,接著是一個覆蓋RET的數據,緊接著是一段shellcode,那只要著個RET地址能指向這段shellcode的第一個指令,那函數返回時就能執行shellcode了。但是軟件的不同版本和不同的運行環境都可能影響這段shellcode在內存中的位置,那么要構造這個RET是十分困難的。一般都在RET和shellcode之間填充大量的NOP指令,使得exploit有更強的通用性。?
├———————┤<—低端內存區域?
│ …… │?
├———————┤<—由exploit填入數據的開始?
│ │?
│ buffer │<—填入無用的數據?
│ │?
├———————┤?
│ RET │<—指向shellcode,或NOP指令的范圍?
├———————┤?
│ NOP │?
│ …… │<—填入的NOP指令,是RET可指向的范圍?
│ NOP │?
├———————┤?
│ │?
│ shellcode │?
│ │?
├———————┤<—由exploit填入數據的結束?
│ …… │?
├———————┤<—高端內存區域?
windows下的動態數據除了可存放在棧中,還可以存放在堆中。了解C++的朋友都知道,C++可以使用new關鍵字來動態分配內存。來看下面的C++代碼:?
#include <stdio.h>?
#include <iostream.h>?
#include <windows.h>?
void func()?
{?
char *buffer=new char[128];?
char bufflocal[128];?
static char buffstatic[128];?
printf("0x%08x\n",buffer); //打印堆中變量的內存地址?
printf("0x%08x\n",bufflocal); //打印本地變量的內存地址?
printf("0x%08x\n",buffstatic); //打印靜態變量的內存地址?
}?
void main()?
{?
func();?
return;?
}?
程序執行結果為:?
0x004107d0?
0x0012ff04?
0x004068c0?
可以發現用new關鍵字分配的內存即不在棧中,也不在靜態數據區。VC編譯器是通過windows下的“堆(heap)”來實現new關鍵字的內存動態分配。在講“堆”之前,先來了解一下和“堆”有關的幾個API函數:?
HeapAlloc 在堆中申請內存空間?
HeapCreate 創建一個新的堆對象?
HeapDestroy 銷毀一個堆對象?
HeapFree 釋放申請的內存?
HeapWalk 枚舉堆對象的所有內存塊?
GetProcessHeap 取得進程的默認堆對象?
GetProcessHeaps 取得進程所有的堆對象?
LocalAlloc?
GlobalAlloc?
當進程初始化時,系統會自動為進程創建一個默認堆,這個堆默認所占內存的大小為1M。堆對象由系統進行管理,它在內存中以鏈式結構存在。通過下面的代碼可以通過堆動態申請內存空間:?
HANDLE hHeap=GetProcessHeap();?
char *buff=HeapAlloc(hHeap,0,8);?
其中hHeap是堆對象的句柄,buff是指向申請的內存空間的地址。那這個hHeap究竟是什么呢?它的值有什么意義嗎?看看下面這段代碼吧:?
#pragma comment(linker,"/entry:main") //定義程序的入口?
#include <windows.h>?
_CRTIMP int (__cdecl *printf)(const char *, ...); //定義STL函數printf?
/*---------------------------------------------------------------------------?
寫到這里,我們順便來復習一下前面所講的知識:?
(*注)printf函數是C語言的標準函數庫中函數,VC的標準函數庫由msvcrt.dll模塊實現。?
由函數定義可見,printf的參數個數是可變的,函數內部無法預先知道調用者壓入的參數個數,函數只能通過分析第一個參數字符串的格式來獲得壓入參數的信息,由于這里參數的個數是動態的,所以必須由調用者來平衡堆棧,這里便使用了__cdecl調用規則。BTW,Windows系統的API函數基本上是__stdcall調用形式,只有一個API例外,那就是wsprintf,它使用__cdecl調用規則,同printf函數一樣,這是由于它的參數個數是可變的緣故。?
---------------------------------------------------------------------------*/?
void main()?
{?
HANDLE hHeap=GetProcessHeap();?
char *buff=HeapAlloc(hHeap,0,0x10);?
char *buff2=HeapAlloc(hHeap,0,0x10);?
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll");?
printf=(void *)GetProcAddress(hMsvcrt,"printf");?
printf("0x%08x\n",hHeap);?
printf("0x%08x\n",buff);?
printf("0x%08x\n\n",buff2);?
}?
執行結果為:?
0x00130000?
0x00133100?
0x00133118?
hHeap的值怎么和那個buff的值那么接近呢?其實hHeap這個句柄就是指向HEAP首部的地址。在進程的用戶區存著一個叫PEB(進程環境塊)的結構,這個結構中存放著一些有關進程的重要信息,其中在PEB首地址偏移0x18處存放的ProcessHeap就是進程默認堆的地址,而偏移0x90處存放了指向進程所有堆的地址列表的指針。windows有很多API都使用進程的默認堆來存放動態數據,如windows 2000下的所有ANSI版本的函數都是在默認堆中申請內存來轉換ANSI字符串到Unicode字符串的。對一個堆的訪問是順序進行的,同一時刻只能有一個線程訪問堆中的數據,當多個線程同時有訪問要求時,只能排隊等待,這樣便造成程序執行效率下降。?
最后來說說內存中的數據對齊。所位數據對齊,是指數據所在的內存地址必須是該數據長度的整數倍,DWORD數據的內存起始地址能被4除盡,WORD數據的內存起始地址能被2除盡,x86 CPU能直接訪問對齊的數據,當他試圖訪問一個未對齊的數據時,會在內部進行一系列的調整,這些調整對于程序來說是透明的,但是會降低運行速度,所以編譯器在編譯程序時會盡量保證數據對齊。同樣一段代碼,我們來看看用VC、Dev-C++和lcc三個不同編譯器編譯出來的程序的執行結果:?
#include <stdio.h>?
int main()?
{?
int a;?
char b;?
int c;?
printf("0x%08x\n",&a);?
printf("0x%08x\n",&b);?
printf("0x%08x\n",&c);?
return 0;?
}?
這是用VC編譯后的執行結果:?
0x0012ff7c?
0x0012ff7b?
0x0012ff80?
變量在內存中的順序:b(1字節)-a(4字節)-c(4字節)。?
這是用Dev-C++編譯后的執行結果:?
0x0022ff7c?
0x0022ff7b?
0x0022ff74?
變量在內存中的順序:c(4字節)-中間相隔3字節-b(占1字節)-a(4字節)。?
這是用lcc編譯后的執行結果:?
0x0012ff6c?
0x0012ff6b?
0x0012ff64?
變量在內存中的順序:同上。?
三個編譯器都做到了數據對齊,但是后兩個編譯器顯然沒VC“聰明”,讓一個char占了4字節,浪費內存哦。?
基礎知識:?
堆棧是一種簡單的數據結構,是一種只允許在其一端進行插入或刪除的線性表。允許插入或刪除操作的一端稱為棧頂,另一端稱為棧底,對堆棧的插入和刪除操作被稱為入棧和出棧。有一組CPU指令可以實現對進程的內存實現堆棧訪問。其中,POP指令實現出棧操作,PUSH指令實現入棧操作。CPU的ESP寄存器存放當前線程的棧頂指針,EBP寄存器中保存當前線程的棧底指針。CPU的EIP寄存器存放下一個CPU指令存放的內存地址,當CPU執行完當前的指令后,從EIP寄存器中讀取下一條指令的內存地址,然后繼續執行。?
總結
以上是生活随笔為你收集整理的代码区,初始化全局数据区,BSS,堆区,栈区,程序环境变量区简介的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 今时今日,C还适合当下之所需么?
- 下一篇: OpenCL结构