轉自 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