也談C 記憶體區域

2021-06-01 07:17:06 字數 3675 閱讀 9972

眾所周知,c++記憶體區域被分為5大類:棧、堆、自由儲存區、全域性/靜態儲存區、常量儲存區。

棧由編譯器控制,棧空間的申請、使用和釋放全權由編譯器處理。這裡的「全權處理」意思是責任歸屬,並不是說編譯器在程式執行時介入管理。實際上,編譯器的工作在編譯期就完成了,它對棧的管理體現在編譯時對暫存器esp的維護上。

堆即程式設計師使用new和delete操作符進行管理的記憶體區域。對堆的訪問往往要比棧要慢,原因在於棧由系統實現,有硬部件上的優勢;而堆只是c++語言中的資料結構,是純粹軟體的,訪問堆上的變數一般都需要棧的參與才能完成。

自由儲存區這個概念存在爭議,爭議的焦點在於它與堆是不是同乙個東西。自由儲存區也由程式設計師管理,但用到的是malloc和free操作。其實,自由儲存區和堆是有區別的:一方面,管理方法不同,malloc和free是函式,new和delete是c++操作符;另一方面,malloc只負責開闢空間,返回指標是空型別指標void*,new不僅開闢空間而且還將呼叫物件建構函式,類似的,free只管釋放空間,delete還將呼叫物件析構函式。

全域性/靜態儲存區正如其名,是全域性變數和靜態變數的集中地。「全域性」著眼於全域性變數的全域性作用域;「靜態」著眼於static變數的生命週期。前者容易理解,後者稍微有些難懂。其實,c++中的動和靜,往往都是從物件的生命週期這個角度去界定的。對於變數來說,「動態」的變數在記憶體區域裡進進出出的變數,它們的生命僅限於自己的作用域,一旦離開自己的作用域就將從記憶體中被擦除,動態變數占用的那片記憶體區域在程式執行過程中有可能被釋放掉;「靜態」的變數則不同,它最大的特點就是隨程式生,隨程式死,步調很一致,只要程式啟動了,靜態變數就在靜態儲存區擁有了自己的空間,程式執行過程中不會被釋放,只有程式結束執行,它們才被釋放。可見,靜態儲存區強調的是變數的生命長度與程式一致。

常量儲存區也很容易理解,即儲存常量的地方。需要注意的是字面數字並不存放在這個區域,常量儲存區里儲存的一般是常量字串。相同的常量字串在常量儲存區只保留乙份拷貝,因此下面這個例子結果為t就容易理解了。

const char* st1="astring";

const char* st2="astring";

cout<

全域性/靜態儲存區預設情況下是一片初始為0x00的區域,因此全域性/靜態變數如果沒被初始化,它將預設被置為0;常量儲存區是一片唯讀區域,不允許程式對裡面任何乙個bit做刪改。

接下來,用乙個簡單的例子進一步說明。例子**如mregions.cpp所示。

view plain

print?

/*++ mregions.cpp

* purpose:區分各種型別資料的記憶體區域

* created:2011-08-19

* author:winterlost

--*/

int a=30;  

void main()    

變數a是全域性變數,b和c是靜態變數,它們被儲存在全域性/靜態儲存區;"astring"和"newstring"是常量字串,它們被儲存在常量儲存區;new int[3]在堆上開闢了可容納3個整型資料的空間,delete p釋放了堆上的空間。棧並沒有特別的舉例,因為程式處處都用到棧。

接下來檢視mregion.cpp對應的disassembly,擷取重要片段如下所示。

view plain

print?

9:     //a,b,c在全域性區  

10:     static int b=10;  

11:     static int c=20;  

12:     int d=40;  

001913ae  mov         dword ptr [d],28h    

13:     //常量區  

14:     const char* str1="astring";  

001913b5  mov         dword ptr [str1],offset string "astring" (195748h)    

15:     const char* str2="newstring";  

001913bc  mov         dword ptr [str2],offset string "newstring" (19573ch)    

16:     //堆  

17:     int* p=new int[3];  

001913c3  push        0ch    

001913c5  call        operator new (191177h)    

001913ca  add         esp,4    

001913cd  mov         dword ptr [ebp-104h],eax    

001913d3  mov         eax,dword ptr [ebp-104h]    

001913d9  mov         dword ptr [p],eax    

18:     p[0]=a;  

001913dc  mov         eax,dword ptr [p]    

001913df  mov         ecx,dword ptr [a (197000h)]    

001913e5  mov         dword ptr [eax],ecx    

19:     p[1]=b;  

001913e7  mov         eax,dword ptr [p]    

001913ea  mov         ecx,dword ptr [b (197004h)]    

001913f0  mov         dword ptr [eax+4],ecx    

20:     p[2]=c;  

001913f3  mov         eax,dword ptr [p]    

001913f6  mov         ecx,dword ptr [c (197008h)]    

001913fc  mov         dword ptr [eax+8],ecx    

21:     delete p;  

001913ff  mov         eax,dword ptr [p]    

00191402  mov         dword ptr [ebp-0f8h],eax    

00191408  mov         ecx,dword ptr [ebp-0f8h]    

0019140e  push        ecx    

0019140f  call        operator delete (191082h)    

00191414  add         esp,4    

注意行號18-20這三句對應的反彙編碼,不難發現變數a/b/c各自對應的記憶體位址為ds:197000h/197004h/197008h,這是它們各自在全域性/靜態資料區占用的記憶體空間位址。觀察14、15行**對應的反彙編碼,可以發現"astring"和"newstring"各自在常量儲存區中的位址為195748h/19573ch。從17行**對應的反彙編碼中看不出new得到的堆首位址,但在vs2010中讓滑鼠懸停在eax上,可檢視eax的值為00101ae0h。而棧的位址,都以暫存器esp為基準浮動。在最終生成的可執行檔案中,看不到變數名a,b,c,str,只有它們在編譯時就被轉換成的位址,用esp表示。

表1 各種型別變數在記憶體中的位址

也談記憶體對齊

一 記憶體對齊的原因 大部分的參考資料都是如是說的 1 平台原因 移植原因 不是所有的硬體平台都能訪問任意位址上的任意資料的 某些硬體平台只能在某些位址處取某些特定型別的資料,否則丟擲硬體異常。2 效能原因 資料結構 尤其是棧 應該盡可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需...

也談記憶體對齊 續

關於記憶體對齊的話題,始終是敏感的。稍有不慎,必將闖下大禍!最近專案稍顯輕閒,自己給自己安排一天反思和總結一下,突然想到以前寫過的一篇 也談記憶體對齊 那篇文章談的是記憶體對齊的基本知識以及一些實驗的資料,想必很多人看完後,會收穫一些東西,但是對記憶體對齊的應用還是處於懵懂狀態,其實大部分時間我們是...

pragma pack n 也談記憶體對齊

在最近的專案中,我們涉及到了 記憶體對齊 技術。對於大部分程式設計師來說,記憶體對齊 對他們來說都應該是 透明的 記憶體對齊 應該是編譯器的 管轄範圍 編譯器為程式中的每個 資料單元 安排在適當的位置上。但是 c語言的乙個特點就是太靈活,太強大,它允許你干預 記憶體對齊 記憶體對齊 對你就不應該再透...