堆栈,数据,文本,heap,bss,text data,stack
堆棧,數據,文本,heap,bss,text data,stack
text data bss stack heap 段
根據APUE,程序分為下面的段:.text, data (initialized), bss, stack, heap。
data/bss/text:
text段在內存中被映射為只讀,但.data和.bss是可寫的。
bss是英文Block Started by Symbol的簡稱,通常是指用來存放程序中未初始化的全局變量的一塊內存區域,在程序載入時由內核清0。BSS段屬于靜態內存分配。初始值也是由用戶自己定義的連接定位文件所確定,用戶應該將定義在可讀寫的RAM區內,源程序中使用malloc分配的內存就是這一塊,不是根據data大小確定,主要由程序中同時分配內存最大值所確定,不過如果超出了范圍,也就是分配失敗,可以等空間釋放之后再分配。
text段是程序代碼段,在AT91庫中是表示程序段的大小,是由編譯器在編譯連接時自動計算的,當你在鏈接定位文件中將該符號放置在代碼段后,那么該符號表示的值就是代碼段大小,編譯連接時,該符號所代表的值會自動代入到源程序中。
data包含靜態初始化的數據,所以有初值的全局變量和static變量在data區。段的起始位置也是由連接定位文件所確定,大小在編譯連接時自動分配,與程序大小沒有關系,但和程序使用到的全局變量,常量數量相關。
stack/heap:
棧(stack)保存函數的局部變量和參數。是一種“后進先出”(Last In First Out,LIFO)的數據結構,這意味著最后放到棧上的數據,將會是第一個從棧上移走的數據。對于哪些暫時存貯的信息,和不需要長時間保存的信息來說,LIFO這種數據結構非常理想。在調用函數或過程后,系統通常會清除棧上保存的局部變量、函數調用信息及其它的信息。棧另外一個重要的特征是,地址空間“向下減少”,即當棧上保存的數據越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區的最后。
堆(heap)保存函數內部動態分配內存,是另外一種用來保存程序信息的數據結構,更準確的說是保存程序的動態變量。堆是“先進先出”(First In first Out,FIFO)數據結構。只允許在堆的一端插入數據,在另一端移走數據。堆的地址空間“向上增加”,即當堆上保存的數據越多,堆的地址就越高。
在程序中的存儲詳情如下:
char *string = “Hello World”;
int iSize;
char *fun(void)
{
char *p;
iSize = 8;
p = malloc(iSize);
return p;
}
存放的位置
Text段:上面標記為紅色的部分(還包括整個fun函數,由于下邊需要,所以沒有標記)都存放在Text段,Text段用來存放代碼(二進制文件)和常量,該段的數據通常是只讀的
例:
Text段:上面標記為紅色的部分(還包括整個fun函數,由于下邊需要,所以沒有標記)都存放在Text段,Text段用來存放代碼(二進制文件)和常量,該段的數據通常是只讀的
例:
-
char *p = “Hello”;
-
p[0] = ‘a’;
VC6.0下,編譯無錯,但運行是提示內存不可寫,這就是因為"Hello"存放在Text段,是只讀的,你不能去修改.
Data段: 已經初始化了的全局變量string就存放在該段,Data段用來存放初始化了的變量,即初始化了的全局變量和靜態變量
BSS段: 未初始化的全局變量iSize存放在這,BSS段主要存放在未初始化的變量,即未初始化的全局變量和靜態變量
Heap段: 程序員手動分配的malloc(iSize)這篇內存存放在該段,也需要用戶自行釋放
Stack段:函數里的局部變量*p存放在該段,Stack段主要存放局部變量、臨時變量、函數相互調用的返回的地址,該段由編譯器自行分配和釋放
接下來來看看,什么被分配了內存,什么時候釋放內存:
全局變量和靜態變量:程序開始的時候就分配了,程序結束時候釋放內存。
局部變量:函數調用的時候為變量分配內存,調用結束釋放內存。
堆里的:調用malloc時分配內存,調用free時釋放。
.text .data .bss .stack .heap 詳解
.text 代碼段:用來存放代碼和常量(const 關鍵字定義的變量)。
.data 數據段:用來存放有初始值的全局變量、全部靜態變量(static 關鍵字定義的變量)。注意全部靜態變量包括全局靜態變量和局部靜態變量,并且不論這些變量是否有初始值。即不管有沒有初始值,也不管是全局變量還是定義在函數內的局部變量,只要是用 static 關鍵字定義的變量,都放在 .data 數據段內。
.bss BSS段:用來存放沒有初始值的全局變量。(沒有初始值的局部變量好像也存在這里,網上驗證帖子,待驗證)。
.stack 棧區: 用來存放局部變量,函數的參數,返回值等,由編譯器自動分配釋放。如一個函數被調用后,產生的臨時變量都會存到棧區的頂部,當函數完成后,會自動從頂部將剛使用的數據銷毀。棧區的地址是從高地址向下增長的。
.heap 堆區: 用來動態內存分配,如 malloc, new 申請的內存,由程序員手動分配釋放。程序中不釋放,則程序結束時,由OS回收;據說這個和數據結構中的堆 沒有什么關系;堆區使用時地址向上增長。
以上5段,在生成的 hex 文件中是怎么分配呢?
Hex 文件分為三部分(可通過 map 文件查看到)
.text 代碼段 -
.data 數據段
- .bss, .stack, .heap的位置信息(即起始位置和大小)
程序運行時,這5段各起什么作用,又在物理存儲空間哪個位置?
現在的計算機架構主要有兩種:
- 馮諾依曼架構:必須從 RAM 中取出執行代碼,即執行代碼要先存入 RAM 中。
- 哈佛架構:可以直接從 flash 執行代碼。
ARM 架構是哈佛架構,可以直接從 flash 取指令執行,所以在程序運行期間:
.text 段位于 flash 內,具體位置和 hex 文件中 .text 段的位置一致,程序運行時直接從 flash 內讀取指令并執行。.text 段都是代碼和常量,只讀,所以可以放在 flash 內。
.data 段在MCU啟動過程中,會被從 flash 內 copy 到 SRAM 內(各家的啟動代碼都會做此操作)。程序運行時是操作 SRAM 中的變量。 .data 段內都是變量,可讀可寫,所以會被放在 SRAM 內。所以 .data 段有兩個地址,一個是在 flash 內的地址,flash 內放的就是變量的初始值;另一個是在 SRAM 內的地址,SRAM 內的地址就是程序運行時變量所在的位置。一個變量初始化的過程,就是啟動代碼將 .data 段從 flash 內 copy 到 SRAM 中的過程,因此 SRAM 中的 .data 區域,在程序運行時(MCU 啟動代碼運行之后)一開始就有值了。這也解釋了,為什么 static 變量的初始化只執行一次(即在編譯時初始化),注意這種通過啟動代碼 copy flash 中內容到對應 SRAM 中的所謂在編譯時初始化,和函數內部局部變量是通過用戶代碼初始化是不一樣的操作,局部變量通過用戶代碼初始化后面會詳細講解。另外要注意,.data 段內數據分有初始值的全局變量和 static 變量兩塊,這兩塊變量雖然都在 .data 段,但作用域不同,全局變量是全局作用域,而 static 變量是局部作用域。
.bss, .stack 和 .heap 段在 hex 文件內有對應的起始位置和大小信息,MCU 在啟動時(啟動代碼)會根據這些信息配置 SRAM,給這三段在 SRAM 中按 hex 文件中的信息分配好對應的位置和大小。程序運行期間,對這三段的操作都是在 SRAM 中進行的。注意 .bss 要初始化,一般是啟動代碼會做這個操作,將整個 .bss 段初始化為 0 (可參考bss段為什么要初始化)。
總結一下這5段在物理存儲器上的位置及占用大小:
Flash 占用大小 = .text 大小 + .data大小 + 其它section(如.bss, .stack, .heap等) 位置信息大小
SRAM 占用大小 = .data 大小 + .bss 大小 + .stack大小 + .heap大小
程序運行時,這 5 段在物理存儲器上的位置如下圖
下面用一段代碼來詳細介紹上面這5段
各變量所在位置如上圖注釋,下面把一些重點概念,如函數中的局部變量如何賦值,用此代碼講解一下:
在編譯時初始化和函數內局部變量用戶代碼初始化
如下圖
全局變量 a 和 靜態變量 e 是在編譯時初始化:這兩個變量都在 .data 段,MCU 啟動時,啟動代碼會將 .data 段從 flash copy 到 SRAM ,copy 操作完成后, SRAM .data 段中的變量就都已經有了初始值(之前存在 flash 中的初始值)。本例中的變量 a 和 c 就是在這個過程中完成的初始化。這個初始化過程中,不需要調用任何用戶代碼,所以被稱作在編譯時初始化。注意:編譯時初始化只初始化一次。因為是啟動代碼執行 copy 操作完成的初始化,啟動代碼只執行一次,所以這個初始化的操作也就只會執行一次。
局部變量 g 在 .stack 中,也就是說,只有在程序運行時,并在調用 main 函數時,才會在 SRAM 的 stack 區域內給變量 g 開出一個空間,讓程序可以對 g 進行操作。那如何對變量 g 賦初始值 2 的呢?是執行 g = 2; 這段代碼,g =2; 這段代碼在 .text 中(存在 flash 中),每次調用 fun 函數時都會調用 g = 2; 這段代碼,這段代碼會給在 stack 中的變量 g 賦初始值 2。但和編譯時初始化不同,每次調用 fun 函數,都會重新對變量 g 做賦值操作,即函數被調用幾次,函數內局部變量的初始化操作就會執行幾次。所以用戶代碼初始化函數內局部變量會初始化多次。
函數內局部變量初始化和函數內靜態變量初始化的區別
局部變量 g 的初始化上面已詳細講解。
局部靜態變量 y 的初始化和 g 不同,靜態變量 y 是在 .data 段中,和全局變量 a 一樣,是用在編譯時初始化的方法完成初始化的。詳細過程參考上面關于變量 a 的初始化過程。
注意:這樣 g 和 y 在初始化值方面的用法就完全不同了。在 fun 函數每次被調用時, g 都被初始化為2,而 y 則不是一直是 3,y 的值是上次 fun 被調用時 y 被賦的值(類似靜態變量 y 使用的實例參考本鏈接)。
用 g 和 y 在 SRAM 中的位置也可以解釋不同:g 處于 SRAM 的 stack 區域,只有 fun 函數被調用時才給 g 分配空間,并且每次分配后,都會對 g 賦初始值 2,所以每次調用 fun 函數,g 初始值都是2,并且函數 fun 執行完后下一句指令(pull)會自動注銷 g 在 stack 中的空間;而 y 處于 SRAM 的 .data 區域,y 在 SRAM中有一個固定的物理空間,即使 fun 函數執行完畢,y 在 SRAM 中還是有一個空間保留著 y 的值,static int y = 3; 這句其實和定義全局變量初始值一樣,只是在編譯時將 y 放在了 .data段,并在 flash 中保存了其初始值 3, 這樣在 MCU 啟動時完成了對 y 的初始化,y 也就只在這時等于 3,之后 fun 執行中只要改變了 y 的值,下次再調用 fun 時,y 中還是上次的值,y=3; 這句代碼完全不會執行。
static 變量詳細介紹和使用可查看本鏈接。
對于常量
c 位置在 .text 段,程序運行期間 c 是在 flash 內,即程序要用到 c 時會去 flash 的 c 地址處直接取值。和全局變量 a 不同,啟動代碼不會 copy c 到 SRAM 中,SRAM 中不會有 c 的位置。
Code, RO, RW, ZI
這些只是用 keil 編譯器時,對 .text, .data, .bss, .statck, .heap 這 5 段的不同稱呼,詳細介紹可參考本鏈接。簡單總結如下:
Code:代碼的大小
RO:常量所占空間
RW:程序中已經初始化的變量所占空間
ZI:未初始化的static和全局變量以及堆棧所占的空間
Flash占用大小=Code+RO+RW
SRAM占用大小=RW+ZI
參考鏈接:
https://blog.csdn.net/phenixyf/article/details/116718762
https://blog.csdn.net/lwbeyond/article/details/7275561#
總結
以上是生活随笔為你收集整理的堆栈,数据,文本,heap,bss,text data,stack的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Swift与LLVM-Clang原理与示
- 下一篇: 自动驾驶参数分析