程式或-記憶體區域分配(五個段)--終於搞明白了
一、
在學習之前我們先看看ELF檔案。
ELF分為三種類型:.o 可重定位檔案(relocalble file),可執行檔案以及共享庫(shared library),三種格式基本上從結構上是一樣的,只是具體到每一個結構不同。下面我們就從整體上看看這3種格式從檔案內容上儲存的方式,spec上有張圖是比較經典的:如上圖:
其實從檔案儲存的格式來說,上面的兩種view實際上是一樣的,Segment實際上就是由section組成的,將相應的一些section對映到一起就叫segment了,就是說segment是由0個或多個section組成的,實際上本質都是section。在這裡我們首先來仔細瞭解一下section和segment的概念:section就是相同或者相似資訊的集合,比如我們比較熟悉的.text .data .bss section,.text是可執行指令的集合,.data是初始化後資料的集合,.bss是未初始化資料的集合。實際上我們也可以將一個程式的所有內容都放在一起,就像dos一樣,但是將可執行程式分成多個section是很有好處的,比如說我們可以將.text
section放在memory的只讀空間內,將可變的.data section放在memory的可寫空間內。
從可執行檔案的角度來講,如果一個數據未被初始化那就不需要為其分配空間,所以.data和.bss一個重要的區別就是.bss並不佔用可執行檔案的大小,它只是記載需要多少空間來儲存這些未初始化資料,而不分配實際的空間。
可以通過命令 $ readelf -l a.out 檢視檔案的格式和組成。
二、
站在組合語言的角度,一個程式分為:
資料段 -- DS
堆疊段 -- SS
程式碼段 -- CS
擴充套件段 -- ES
站在高階語言的角度,根據APUE,一個程式分為如下段:
text
data (initialized)
bss
stack
heap
1.一般情況下,一個可執行二進位制程式
★★可執行二進位制程式
= 程式碼段(text)+資料段(data)+BSS段★★
2.而當程式被載入到記憶體單元時,則需要另外兩個域:堆域和棧域。圖1-1所示為可執行程式碼儲存態和執行態的結構對照圖。一個正在執行的C程式佔用的記憶體區域分為程式碼段、初始化資料段、未初始化資料段(BSS)、堆、棧5個部分。
★★正在執行的C程式 = 程式碼段+初始化資料段(data)
3.在將應用程式載入到記憶體空間執行時,作業系統負責程式碼段、資料段和BSS段的載入,並將在記憶體中為這些段分配空間。棧亦由作業系統分配和管理,而不需要程式設計師顯示地管理;堆段由程式設計師自己管理,即顯示地申請和釋放空間。
4.動態分配與靜態分配,二者最大的區別在於:1. 直到Run-Time的時候,執行動態分配,而在compile-time的時候,就已經決定好了分配多少Text+Data+BSS+Stack。2.通過malloc()動態分配的記憶體,需要程式設計師手工呼叫free()釋放記憶體,否則容易導致記憶體洩露,而靜態分配的記憶體則在程序執行結束後系統釋放(Text,
Data), 但Stack段中的資料很短暫,函式退出立即被銷燬。
圖1-1(從可執行檔案a.out的角度來講,如果一個數據未被初始化那就不需要為其分配空間,所以.data和.bss一個重要的區別就是.bss並不佔用可執行檔案的大小,它只是記載需要多少空間來儲存這些未初始化資料,而不分配實際的空間)
三、
程式碼段 --text(code
segment/text segment)
text段在記憶體中被對映為只讀,但.data和.bss是可寫的。
text段是程式程式碼段,在AT91庫中是表示程式段的大小,它是由編譯器在編譯連線時自動計算的,當你在連結定位檔案中將該符號放置在程式碼段後,那麼該符號表示的值就是程式碼段大小,編譯連線時,該符號所代表的值會自動代入到源程式中。
資料段 -- data
data包含靜態初始化的資料,所以有初值的全域性變數和static變數在data區。段的起始位置也是由連線定位檔案所確定,大小在編譯連線時自動分配,它和你的程式大小沒有關係,但和程式使用到的全域性變數,常量數量相關。資料段屬於靜態記憶體分配。
bss段--bss
bss是英文Block Started by Symbol的簡稱,通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域,在程式載入時由核心清0。BSS段屬於靜態記憶體分配。它的初始值也是由使用者自己定義的連線定位檔案所確定,使用者應該將它定義在可讀寫的RAM區內,源程式中使用malloc分配的記憶體就是這一塊,它不是根據data大小確定,主要由程式中同時分配記憶體最大值所確定,不過如果超出了範圍,也就是分配失敗,可以等空間釋放之後再分配。BSS段屬於靜態記憶體分配。
stack:
棧(stack)儲存函式的區域性變數(但不包括static宣告的變數,static意味著在資料段中存放變數),引數以及返回值。是一種“後進先出”(Last
In First Out,LIFO)的資料結構,這意味著最後放到棧上的資料,將會是第一個從棧上移走的資料。對於哪些暫時存貯的資訊,和不需要長時間儲存的資訊來說,LIFO這種資料結構非常理想。在呼叫函式或過程後,系統通常會清除棧上儲存的區域性變數、函式呼叫資訊及其它的資訊。棧另外一個重要的特徵是,它的地址空間“向下減少”,即當棧上儲存的資料越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區的最後。
heap:
堆(heap)儲存函式內部動態分配記憶體,是另外一種用來儲存程式資訊的資料結構,更準確的說是儲存程式的動態變數。堆是“先進先出”(First In first Out,FIFO)資料結構。它只允許在堆的一端插入資料,在另一端移走資料。堆的地址空間“向上增加”,即當堆上儲存的資料越多,堆的地址就越高。
下圖是APUE中的一個典型C記憶體空間分佈圖:
所以可以知道傳入的引數,區域性變數,都是在棧頂分佈,隨著子函式的增多而向下增長.
函式的呼叫地址(函式執行程式碼),全域性變數,靜態變數都是在分配記憶體的低部存在,而malloc分配的堆則存在於這些記憶體之上,並向上生長.
舉例1:
- #include <stdio h="">
- constint g_A = 10; //程式碼段
- int g_B = 20; //資料段
- staticint g_C = 30; //資料段
- staticint g_D; //BSS段
- int g_E; //BSS段
- char *p1; //BSS段
- void main( )
- {
- int local_A; //棧
- int local_B; //棧
- staticint local_C = 0; //資料段
- staticint local_D; //資料段
- char *p3 = "123456"; //123456在程式碼段,p3在棧上
- p1 = (char *)malloc( 10 ); //堆,分配得來得10位元組的區域在堆區
- strcpy( p1, "123456" ); //123456{post.content}放在常量區,編譯器可能會將它與p3所指向 的"123456"優化成一塊
- printf("hight address\n");
- printf("-------------棧--------------\n");
- printf( "棧, 區域性變數, local_A, addr:0x%08x\n", &local_A );
- printf( "棧, 區域性變數,(後進棧地址相對local_A低) local_B, addr:0x%08x\n", &local_B );
- printf("-------------堆--------------\n");
- printf( "堆, malloc分配記憶體, p1, addr:0x%08x\n", p1 );
- printf("------------BSS段------------\n");
- printf( "BSS段, 全域性變數, 未初始化 g_E, addr:0x%08x\n", &g_E, g_E );
- printf( "BSS段, 靜態全域性變數, 未初始化, g_D, addr:0x%08x\n", &g_D );
- printf( "BSS段, 靜態區域性變數, 初始化, local_C, addr:0x%08x\n", &local_C);
- printf( "BSS段, 靜態區域性變數, 未初始化, local_D, addr:0x%08x\n", &local_D);
- printf("-----------資料段------------\n");
- printf( "資料段,全域性變數, 初始化 g_B, addr:0x%08x\n", &g_B);
- printf( "資料段,靜態全域性變數, 初始化, g_C, addr:0x%08x\n", &g_C);
- printf("-----------程式碼段------------\n");
- printf( "程式碼段,全域性初始化變數, 只讀const, g_A, addr:0x%08x\n\n", &g_A);
- printf("low address\n");
- return;
- }
- </stdio>
執行結果:
- hight address
- -------------棧--------------
- 棧, 區域性變數, local_A, addr:0xffa70c1c
- 棧, 區域性變數,(後進棧地址相對local_A低) local_B, addr:0xffa70c18
- -------------堆--------------
- 堆, malloc分配記憶體, p1, addr:0x087fe008
- ------------BSS段------------
- BSS段, 全域性變數, 未初始化 g_E, addr:0x08049a64
- BSS段, 靜態全域性變數, 未初始化, g_D, addr:0x08049a5c
- BSS段, 靜態區域性變數, 初始化, local_C, addr:0x08049a58
- BSS段, 靜態區域性變數, 未初始化, local_D, addr:0x08049a54
- -----------資料段------------
- 資料段,全域性變數, 初始化 g_B, addr:0x08049a44
- 資料段,靜態全域性變數, 初始化, g_C, addr:0x08049a48
- -----------程式碼段------------
- 程式碼段,全域性初始化變數, 只讀const, g_A, addr:0x08048620
- low address
注意:
編譯時需要-g選項,這樣才可以看elf資訊;
readelf -a a.out
檢視這個執行檔案的elf資訊,摘錄部分如下:重點注意其中data段,text段還要有bss段的地址,然後比較這個地址和上面的執行結果,是否是在elf檔案的各個段的地址之內。
- Section Headers:
- [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
- [ 0] NULL 00000000 000000 000000 00 0 0 0
- [ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1
- [ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4
- [ 3] .gnu.hash GNU_HASH 08048148 000148 000020 04 A 4 0 4
- [ 4] .dynsym DYNSYM 08048168 000168 000070 10 A 5 1 4
- [ 5] .dynstr STRTAB 080481d8 0001d8 000058 00 A 0 0 1
- [ 6] .gnu.version VERSYM 08048230 000230 00000e 02 A 4 0 2
- [ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1 4
- [ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0 4
- [ 9] .rel.plt REL 08048268 000268 000028 08 A 4 11 4
- [10] .init PROGBITS 08048290 000290 000017 00 AX 0 0 4
- [11] .plt PROGBITS 080482a8 0002a8 000060 04 AX 0 0 4
- [12] .text PROGBITS 08048310 000310 0002e8 00 AX 0 0 16
- [13] .fini PROGBITS 080485f8 0005f8 00001c 00 AX 0 0 4
- [14] .rodata PROGBITS 08048614 000614 000326 00 A 0 0 4
- [15] .eh_frame PROGBITS 0804893c 00093c 000004 00 A 0 0 4
- [16] .ctors PROGBITS 08049940 000940 000008 00 WA 0 0 4
- [17] .dtors PROGBITS 08049948 000948 000008 00 WA 0 0 4
- [18] .jcr PROGBITS 08049950 000950 000004 00 WA 0 0 4
- [19] .dynamic DYNAMIC 08049954 000954 0000c8 08 WA 5 0 4
- [20] .got PROGBITS 08049a1c 000a1c 000004 04 WA 0 0 4
- [21] .got.plt PROGBITS 08049a20 000a20 000020 04 WA 0 0 4
- [22] .data PROGBITS 08049a40 000a40 00000c 00 WA 0 0 4
- [23] .bss NOBITS 08049a4c 000a4c 00001c 00 WA 0 0 4
- [24] .comment PROGBITS 00000000 000a4c 000114 00 0 0 1
- [25] .debug_aranges PROGBITS 00000000 000b60 000020 00 0 0 1
- [26] .debug_pubnames PROGBITS 00000000 000b80 00003a 00 0 0 1
- [27] .debug_info PROGBITS 00000000 000bba 0001f4 00 0 0 1
- [28] .debug_abbrev PROGBITS 00000000 000dae 00006f 00 0 0 1
- [29] .debug_line PROGBITS 00000000 000e1d 000058 00 0 0 1
- [30] .debug_frame PROGBITS 00000000 000e78 00003c 00 0 0 4
- [31] .debug_str PROGBITS 00000000 000eb4 00000d 00 0 0 1
- [32] .debug_loc PROGBITS 00000000 000ec1 000043 00 0 0 1
- [33] .shstrtab STRTAB 00000000 000f04 000143 00 0 0 1
- [34] .symtab SYMTAB 00000000 0015e8 000560 10 35 60 4
- [35] .strtab STRTAB 00000000 001b48 0002ad 00 0 0 1
- Key to Flags:
- W (write), A (alloc), X (execute), M (merge), S (strings)
- I (info), L (link order), G (group), x (unknown)
- O (extra OS processing required) o (OS specific), p (processor specific)
★★★★注意靜態變數初始化為零和全域性靜態變數初始化為零的情況,都是儲存在bss段★★★★
從上面的elf檔案可以看出,
[23] .bss NOBITS 08049a4c 000a4c
00001c 00 WA 0 0 4
[22] .data PROGBITS 08049a40000a40 00000c 00 WA 0 0 4
[12] .text PROGBITS 08048310000310
0002e8 00 AX 0 0 16
但是在結果中顯示: BSS段, 靜態區域性變數, 初始化, local_C, addr:0x08049a58
(0x08049a58 大於0x08049a4c 屬於bss段)是初始化的靜態區域性變數但是卻屬於bss段,為什麼?
原因是:local_C是區域性靜態變數但是卻初始化為零。這和沒有初始化,預設是零的情況一樣,都儲存在bss段,如果初始化為其他的值,那麼local_C這個變數就會儲存在data段。
四、
可執行檔案大小由什麼決定?
可執行檔案在儲存時分為程式碼段、資料段和BSS段三個部分。
【例一】
程式1:
int ar[30000];
void main()
{
......
}
程式2:
int ar[300000] = {1, 2, 3, 4, 5, 6 };
void main()
{
......
}
發現程式2編譯之後所得的.exe檔案比程式1的要大得多。當下甚為不解,於是手工編譯了一下,並使用了/FAs編譯選項來查看了一下其各自的.asm,發現在程式1.asm中ar的定義如下:
_BSS SEGMENT
[email protected]@3PAHA DD 0493e0H DUP (?) ; ar
_BSS ENDS
而在程式2.asm中,ar被定義為:
_DATA SEGMENT
[email protected]@3PAHA DD 01H ; ar
DD 02H
DD 03H
ORG $+1199988
_DATA ENDS
區別很明顯,一個位於.bss段,而另一個位於.data段,兩者的區別在於:全域性的未初始化變數存在於.bss段中,具體體現為一個佔位符;全域性的已初始化變數存於.data段中;而函式內的自動變數都在棧上分配空間。
.bss是不佔用.exe檔案空間的,其內容由作業系統初始化(清零);而.data卻需要佔用,其內容由程式初始化,因此造成了上述情況。
以上僅僅做為學習只用!!
參考材料: C程式記憶體區域分配(5個段作用)相關推薦
程式或-記憶體區域分配(五個段)--終於搞明白了
一、 在學習之前我們先看看ELF檔案。 ELF分為三種類型:.o 可重定位檔案(relocalble file),可執行檔案以及共享庫(shared library),三種格式基本上從結構上是一樣的,只是具體到每一個結構不同。下面我們就從整體上看看這3種格式從檔案內容上儲存的方式,spec上有張圖是比較經
程式或-記憶體區域分配(五個段)
一. 在學習之前我們先看看ELF檔案。 ELF分為三種類型:.o 可重定位檔案(relocalble file),可執行檔案以及共享庫(shared library),三種格式基本上從結構上是一樣的,只是具體到每一個結構不同。下面我們就從整體上看看這3種格式從檔案內容上儲存的方式,spec上
Intellij IDEA控制檯提示:mvn不是內部或外部命令,也不是可執行的程式或批處理檔案(最全總結)
解決方案有兩種: 一、1、如果沒有安裝maven:在IDEA中使用maven,提示mvn不是內部命令,需要在環境變數中的使用者變數的Path中新增maven的bin路徑,重啟下IDEA即可, 1、環境變數(使用者); 2、Path,新增IDEA下的maven的bin
C/C++程式編譯時和執行時記憶體區域分配
3.heap區,存放內容和上文同。值得說明的是:stack區起始地址是在高地址,即是從高地址向低地址延伸。而heap區起始地址是在低地址,即是從低地址向高地址延伸。總結:stack起始地址固定在高地址,heap起始地址固定在低地址,然後兩個區都向中間延伸。直到stack區和heap區的結束
使用CefSharp在.Net程式中嵌入Chrome瀏覽器(五)——Javascript互動
要在CEF中和網頁的JS進行互動,首先我們要通過設定啟用Javascrit整合功能。 CefSharpSettings.LegacyJavascriptBindingEnabled = true; 呼叫JavaScript: 簡單的呼叫JavaScript可以直接
小程式頁面傳參(多個引數)
小程式頁面傳遞引數 這一次講一下,跨頁面傳值和在下一個頁面接收值,接收以後並渲染展示出來。那麼請看清楚我這提到的步驟1.A頁面傳值,傳多值到下一個頁面 2.B頁面接收上一個頁面傳輸過來的值; 3.在B頁面渲染接收到 的資料 那麼直接上程式碼,先看看我想實現的效果。首先我想實現把這個test頁面的
微信小程式中換行,空格(多個空格)寫法
在小程式中HTML的網頁實體無法正常使用,小程式中的寫法為: 一、空格,換行 <text>你好!\t七月流火啊!\n我在下一行</text> ---------------------------------------------------------
linux ps sort命令檢視佔用CPU或記憶體最大的幾個程序
ps aux | sort -k 3 -rn |head 檢視系統內佔用cpu最大的幾個程序 ps aux | sort -k 4 -rn |head 檢視系統內佔用記憶體最大的幾個程序 sort 升序排列 引數:
儲存器管理——記憶體分配(分割槽,段,頁)
儲存器管理 連續分割槽兩種方式對比 固定分割槽 可變分割槽 分割槽記憶體大小 固定 可變 記憶體利用率 低 提高
java 陣列實現動態的記憶體分配(繼承的應用)
如何實現動態分配? 說白了其實就是陣列大小由外部輸入,然後在動態生成陣列。 因此在新增元素時需要判斷陣列是否已經滿員了,所以類中需要一個記錄了陣列大小的標記,這裡記為font 我以下的程式碼寫了一個Array的父類和兩個繼承自它的子類SortArray可以實現陣
Linux程序的五個段(資料段、程式碼段、bss、堆疊段)
BSS段:BSS段(bss segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態記憶體分配。 資料段:資料段(data segment)通常是指用來存放程式中已初始化的
Java記憶體模型FAQ(五)舊的記憶體模型有什麼問題?
譯者:Alex 舊的記憶體模型中有幾個嚴重的問題。這些問題很難理解,因此被廣泛的違背。例如,舊的儲存模型在許多情況下,不允許JVM發生各種重排序行為。舊的記憶體模型中讓人產生困惑的因素造就了JSR-133規範的誕生。 例如,一個被廣泛認可的概念就是,如果使用final欄位,那麼就沒有必要在
iOS開發中的記憶體分配(堆和棧)
程序的記憶體分割槽 所有程序(執行的程式)都必須佔用一定數量的記憶體,它或是用來存放從磁碟載入的程式程式碼,或是存放取自使用者輸入的資料等等。不過程序對這些記憶體的管理方式因記憶體用途不一而不盡相同,有些記憶體是事先靜態分配和統一回收的,而有些卻是按需要動態分配和回收的
JVM記憶體區域劃分(JDK6 7 8中的變化)
在Java7之前,HotSpot虛擬機器中將GC分代收集擴充套件到了方法區,使用永久代來實現了方法區。這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝。但是在之後的HotSpot
深入JVM 原理(五)Java堆記憶體調整引數(調優關鍵)
目錄 堆記憶體的引數調整 通過之前的分析可以發現,實際上每一塊子記憶體區中都會存在有一部分的可變伸縮區,其基本流程: 如果空間不足,在可變的範圍之內擴大記憶體空間,當一段時間之後發現記憶體空間沒有這麼緊張的時候,再將可變空間進行釋放。所以在整個調整
[jvm解析系列][一]Java記憶體區域分配和記憶體溢位異常OOM
學過作業系統的同學應該比較清楚,一個作業系統必須要有完善的記憶體管理系統(頁/段式的管理),相應的jvm全稱java虛擬機器應該也有類似的一種管理記憶體的方式,這種方式是建立在真實的作業系統記憶體管理方式之上的,他把記憶體分配成了不同的區域,形成了java記憶體模型。 那麼
JVM的記憶體區域劃分(面試問題:你瞭解java記憶體模型麼)
JVM的記憶體區域劃分 學過C語言的朋友都知道C編譯器在劃分記憶體區域的時候經常將管理的區域劃分為資料段和程式碼段,資料段包括堆、棧以及靜態資料區。那麼在Java語言當中,記憶體又是如何劃分的呢? 由於Java程式是交由JVM執行的,所以我們在談Java記憶
圖解如何將Ubuntu上的Qt程式交叉編譯出可在ARM平臺執行的程式(五部曲)
圖解如何將Ubuntu上的Qt程式交叉編譯出可在ARM平臺執行的程式(五部曲) (原創作品,轉載時請註明出處,商業用途前應徵得作者同意) 作者: devinoy(百度使用者) 日期:2012-10-16 版本:V1.00 Ubuntu(以ubuntu12.04為例,
記憶體分配(首次適應演算法)
首次適應演算法: 使用該演算法進行記憶體分配時,從空閒分割槽鏈首開始查詢,直至找到一個能滿足其大小需求的空閒分割槽為止;然後再按照作業的大小,從該分割槽中劃出一塊記憶體分配給請求者,餘下的空閒分割槽仍留在空閒分割槽鏈中。 該演算法傾向於使用記憶體中低地址部分的空閒分
記憶體分配(malloc()和free())
C語言的一個特性是接近底層,對於硬體的控制能力比其他高階動態語言要強。同時,C語言賦予程式設計師更大的自由度,更信任程式設計師。在記憶體的分配與釋放上,我們知道非靜態變數(塊作用域,無連結,自動生存期)在程式進入到變數定義所在的地方(塊或函式內)時分配記憶體,在離開塊作用域時釋放。對於靜態變數,在程式載入到記