C/C++程序內存的各種變量存儲區域和各個區域詳解


轉自 https://blog.csdn.net/jirryzhang/article/details/79518408

C語言在內存中一共分為如下幾個區域,分別是:

1. 內存棧區: 存放局部變量名;
2. 內存堆區: 存放new或者malloc出來的對象;
3. 常數區: 存放局部變量或者全局變量的值;
4. 靜態區: 用於存放全局變量或者靜態變量;
5. 代碼區:二進制代碼。
知道如上一些內存分配機制,有助於我們理解指針的概念。

C/C++不提供垃圾回收機制,因此需要對堆中的數據進行及時銷毀,防止內存泄漏,使用free和delete銷毀new和malloc申請的堆內存,而棧內存是動態釋放。

全局變量、靜態局部變量保存在全局數據區,初始化的和未初始化的分別保存在一起;

普通局部變量保存在堆棧中;

全局變量和局部變量在內存里的區別? 

一、預備知識—程序的內存分配 

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

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

2、堆區(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表,呵呵。 


3、全局區(靜態區)(static)—,全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域(RW), 未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域(ZI)。 - 程序結束后有系統釋放 

4、文字常量區 —常量字符串就是放在這里的。 程序結束后由系統釋放 (RO)

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

注:

1、對於RO、RW和WI的概念不是特別清楚的朋友,可以參考我的另外一篇文章,里邊有詳細的解釋。

2、按我個人理解為了減少內存碎片的產生,編譯器可能會將堆區又分為block和heap區。block由一系列大小相等的內存塊組成。分配內存時先在block中分配,如果block占滿則從heap區中分配。同時block的大小和個數可以通過配置文件進行配置,使之達到一個合適的數量。

例:

int a = 0; 全局初始化區 
 
char *p1; 全局未初始化區 
 
main() 
 
{ 
 
int b;//
 
char s[] = "abc"; //"abc"在常量區,s在棧上。 
 
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"優化成一個地方。 
 
}

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

 

1、棧區(stack)— 程序運行時由編譯器自動分配,存放函數的參數值,局部變量的值等。

        其操作方式類似於數據結構中的棧。

 

2、堆區(heap) — 在內存開辟另一塊存儲區域。

      一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。

      注意它與數據結構中的堆是兩回事,分配方式倒是類似於鏈表。

 

3、全局區(靜態區)(static)—編譯器編譯時即分配內存。

       全局變量和靜態變量的存儲是放在一塊的,

       初始化的全局變量和靜態變量在一塊區域,

        未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。 

       - 程序結束后由系統釋放

 

4、文字常量區 —常量字符串就是放在這里的。 程序結束后由系統釋放。

 

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


注意:靜態局部變量和靜態全局變量

屬於靜態存儲方式的量不一定就是靜態變量。 

例如:全局變量雖屬於靜態存儲方式,但不一定是靜態變量,

           必須由 static加以定義后才能成為靜態外部變量,或稱靜態全局變量。

把局部變量改變為靜態變量后是改變了它的存儲方式,即改變了它的生存期。
把全局變量改變為靜態變量后是改變了它的作用域,限制了它的使用范圍。

楔子

 

一個可執行程序文件需要在計算機硬件上運行起來,其實質就是靜態的文件被加載到內存中的過程,可執行程序文件只是一個程序的載體。那么執行一個應用后,它在內存中是一個怎樣的結構呢,請關注今天的走進科學——《C/C++ 程序內存結構》。

動&靜

 

一個程序被加載到內存中,這塊內存首先就存在兩種屬性:靜態分配內存和動態分配內存。 
靜態分配內存:是在程序編譯和鏈接時就確定好的內存。 
動態分配內存:是在程序加載、調入、執行的時候分配/回收的內存。

Text & Data & Bss


.text: 也稱為代碼段(Code),用來存放程序執行代碼,同時也可能會包含一些常量(如一些字符串常量等)。該段內存為靜態分配,只讀(某些架構可能允許修改)。 
這塊內存是共享的,當有多個相同進程(Process)存在時,共用同一個text段。

.data: 也有的地方叫GVAR(global value),用來存放程序中已經初始化的非零全局變量。靜態分配。

data又可分為讀寫(RW)區域和只讀(RO)區域。 


-> RO段保存常量所以也被稱為.constdata 
-> RW段則是普通非常全局變量,靜態變量就在其中
.bss: 存放程序中為初始化的和零值全局變量。靜態分配,在程序開始時通常會被清零。

text和data段都在可執行文件中,由系統從可執行文件中加載;而bss段不在可執行文件中,由系統初始化。 
這三段內存就組成了我們編寫的程序的本體,但是一個程序運行起來,還需要更多的數據和數據間的交互,否則這個程序就是死的,無用的。所以我們還需要為更多的數據和數據交互提供一塊內存——堆棧。

 

堆棧(Heap& Stack)


堆和棧都是動態分配內存,兩者空間大小都是可變的。

Stack: 棧,存放Automatic Variables,按內存地址由高到低方向生長,其最大大小由編譯時確定,速度快,但自由性差,最大空間不大。

Heap: 堆,自由申請的空間,按內存地址由低到高方向生長,其大小由系統內存/虛擬內存上限決定,速度較慢,但自由性大,可用空間大。 
每個線程都會有自己的棧,但是堆空間是共用的。

Tips:

char* p = new char[20];
// 這行代碼在Heap中開辟了20個char長度的空間,同時在Stack上壓入了p,
// 指針變量p存在於棧上,其值為剛剛在堆上開辟的空間的首地址。

 

圖解
在 sw-at 的博客上扒了一張圖,這張圖中所示內存空間,地址由下往上增長,分別標示了 .text、.data、.bss、stack和heap的內存分部情況。 
我們可以看到:

 

 

text、data(gvar)、bss 在內存中地址較低低的位置(low level address),而堆棧則在相對較搞的位置。
堆(Heap)往高地址方向生長,棧(Stack)往低地址方向生長。

 


在C\C++中,通常可以把內存理解為4個分區:棧、堆、全局/靜態存儲區和常量存儲區。下面我們分別簡單地介紹一下各自的特點。

1 棧     

  

通常是用於那些在編譯期間就能確定存儲大小的變量的存儲區,用於在函數作用域內創建,在離開作用域后自動銷毀的變量的存儲區。通常是局部變量,函數參數等的存儲區。他的存儲空間是連續的,兩個緊密挨着定義的局部變量,他們的存儲空間也是緊挨着的。棧的大小是有限的,通常Visual C++編譯器的默認棧的大小為1MB,所以不要定義int a[1000000]這樣的超大數組。

2 堆


        通常是用於那些在編譯期間不能確定存儲大小的變量的存儲區,它的存儲空間是不連續的,一般由malloc(或new)函數來分配內存塊,並且需要用free(delete)函數釋放內存。如果程序員沒有釋放掉,那么就會出現常說的內存泄漏問題。需要注意的是,兩個緊挨着定義的指針變量,所指向的malloc出來的兩塊內存並不一定的是緊挨着的,所以會產生內存碎片。另外需要注意的一點是,堆的大小幾乎不受限制,理論上每個程序最大可達4GB。

3 全局/靜態存儲區


        和“棧”一樣,通常是用於那些在編譯期間就能確定存儲大小的變量的存儲區,但它用於的是在整個程序運行期間都可見的全局變量和靜態變量。

4 常量存儲區


        和“全局/靜態存儲區”一樣,通常是用於那些在編譯期間就能確定存儲大小的常量的存儲區,並且在程序運行期間,存儲區內的常量是全局可見的。這是一塊比較特殊的存儲去,他們里面存放的是常量,不允許被修改。

5 總結


        根據上面的內容,分別將棧和堆、全局/靜態存儲區和常量存儲區進行對比,結果如下。

 

 

 

l          棧區:主要用來存放局部變量, 傳遞參數, 存放函數的返回地址。.esp 始終指向棧頂, 棧中的數據越多, esp的值越小。

l          堆區:用於存放動態分配的對象, 當你使用 malloc和new 等進行分配時,所得到的空間就在堆中。動態分配得到的內存區域附帶有分配信息, 所以你能夠 free和delete它們。

l          數據區:全局,靜態和常量是分配在數據區中的,數據區包括bss(未初始化數據區)和初始化數據區。

注意:

1)     堆向高內存地址生長;

2)     棧向低內存地址生長;

3)     堆和棧相向而生,堆和棧之間有個臨界點,稱為stkbrk。

 

1、一條進程在內存中的映射

 

    假設現在有一個程序,它的函數調用順序如下:

main(...) ->; func_1(...) ->; func_2(...) ->; func_3(...),即:主函數main調用函數func_1; 函數func_1調用函數func_2; 函數func_2調用函數func_3。

當一個程序被操作系統調入內存運行, 其對應的進程在內存中的映射如下圖所示:

 

 

 


注意:

l          隨着函數調用層數的增加,函數棧幀是一塊塊地向內存低地址方向延伸的;

l          隨着進程中函數調用層數的減少(即各函數調用的返回),棧幀會一塊塊地被遺棄而向內存的高址方向回縮;

l          各函數的棧幀大小隨着函數的性質的不同而不等, 由函數的局部變量的數目決定。

 

l          未初始化數據區(BSS):用於存放程序的靜態變量,這部分內存都是被初始化為零的;而初始化數據區用於存放可執行文件里的初始化數據。這兩個區統稱為數據區。

 

l          Text(代碼區):是個只讀區,存放了程序的代碼。任何嘗試對該區的寫操作會導致段違法出錯。代碼區是被多個運行該可執行文件的進程所共享的。   

 

l          進程對內存的動態申請是發生在Heap(堆)里的。隨着系統動態分配給進程的內存數量的增加,Heap(堆)有可能向高址或低址延伸, 這依賴於不同CPU的實現,但一般來說是向內存的高地址方向增長的。

 

l          在未初始化數據區(BSS)或者Stack(棧區)的增長耗盡了系統分配給進程的自由內存的情況下,進程將會被阻塞, 重新被操作系統用更大的內存模塊來調度運行。

 

l          函數的棧幀:包含了函數的參數(至於被調用函數的參數是放在調用函數的棧幀還是被調用函數棧幀, 則依賴於不同系統的實現)。函數的棧幀中的局部變量以及恢復該函數的主調函數的棧幀(即前一個棧幀)所需要的數據, 包含了主調函數的下一條執行指令的地址。

 

 

2、  函數的棧幀

    函數調用時所建立的棧幀包含下面的信息:

1)     函數的返回地址。返回地址是存放在主調函數的棧幀還是被調用函數的棧幀里,取決於不同系統的實現;

2)     主調函數的棧幀信息, 即棧頂和棧底;

3)     為函數的局部變量分配的棧空間;

4)     為被調用函數的參數分配的空間取決於不同系統的實現。

 

注意:

l          BSS區(未初始化數據段):並不給該段的數據分配空間,僅僅是記錄了數據所需空間的大小。

l          DATA(初始化的數據段):為數據分配空間,數據保存在目標文件中。

 


基本上程序員在開始接觸Linux編程時就大抵就都聽過代碼段、數據段等等概念,它們是各種數據存放的位置。通過objdump -h命令可以查看一個.o文件(已編譯成二進制文件但未鏈接)的各個段:

 

 

 

1. 代碼段(.txt)


  .txt段存放代碼(如函數)與部分整數常量,.txt段的數據可以被執行

2. 數據段(.data)


  .data用於存放初始化過的全局變量。若全局變量值為0,為了優化編譯器會將它放在.bss段中

3. bss段(.bss)


  .bss段被用來存放那些沒有初始化或者初始化為0的全局變量。bss段只占運行時的內存空間而不占文件空間。在程序運行的整個周期內,.bss段的數據一直存在

  .data和.bss段的區別可以通過下面程序驗證:

#include <stdio.h>

char global_arr[1024 * 1024]; //存放在.bss段
int main(void)
{
return 0;
}

 

  編譯后查看大小: 

 

 


  顯然,global_arr數組占據的1M空間並沒有占據文件空間。將global_arr數組改放在.data段中:

char global_arr[1024 * 1024] = {4}; //存放在.data段

 


  編譯后查看大小: 
 

 

 


  文件變成了1M多,顯然.data段上的數據是占據文件空間的。

4. 常量數據段(.rodata)


  ro表read only,用於存放不可變修改的常量數據,一旦程序中對其修改將會出現段錯誤: 
  (1) 程序中的常量不一定就放在rodata中,有的立即數和指令編碼放在.text中 
  (2) 對於字符串常量,若程序中存在重復的字符串,編譯器會保證只存在一個 
  (3) rodata是在多個進程間共享的 
  (4) 有的嵌入式系統,rodata放在ROM(或者NOR FLASH)中,運行時直接讀取無需加載至RAM( 哈佛和馮諾依曼,從STM32的const全局變量說起有所記錄 https://blog.csdn.net/qq_29344757/article/details/75730054
想要將數據放在.rodata只需要加上const屬性修飾即可。

5. 棧


  棧是用於存放臨時變量和函數調用的。棧也是一種先進后出的數據結構,函數的遞歸調用正得益於棧的存在。需注意存在棧的數據只在當前函數和子函數中有效,一旦函數返回數據將會被自動釋放。

6. 堆


  堆的使用周期有使用者控制,程序中的內存泄漏多因程序員對堆的管理不當引起,需謹慎。

7. .comment段


  在上圖中還看到.comment段,它存放的是編譯器版本等信息。除了.comment,還有.note、.hash等其他段,了解即可。
————————————————
版權聲明:本文為CSDN博主「jirryzhang」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/jirryzhang/article/details/79518408


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM