用ram實現暫存器堆 C 堆與棧詳解

2021-10-11 14:06:04 字數 4474 閱讀 4994

大多數作業系統會將記憶體空間分為核心空間和使用者空間,而每個程序的記憶體空間又有如下的「預設」區域。

2、堆:堆用來容納應用程式動態分配的記憶體區域,我們使用malloc 或者new分配記憶體時,得到的記憶體來自堆裡。堆通常存於棧的下方(低位址方向),堆一般比棧大很多,可以有幾十至數百兆位元組的容量。

3、可執行檔案映象:可執行檔案由裝載器在裝載時將可執行檔案讀取到記憶體或者對映到記憶體。

4、保留區:保留區並不是乙個單一的記憶體區域,而是對記憶體中受保護而禁止訪問的記憶體區域的總稱,例如,大多說作業系統中,極小的位址通常都是不允許訪問的,如null。(順便提一下,我們程式設計的時候經常會遇到『段錯誤(segment fault)』或者『非法操作,該記憶體位址不能read/write』的錯誤資訊,其中乙個原因就是我們初始化了乙個指標為null但是沒有給它賦合理的值就開始使用它。)

linux 程序記憶體空間布局如下圖:

01 棧

乙個棧的例項:

在經典的作業系統中,棧總是向下增長的,棧頂由稱為esp的暫存器進行定位,壓棧的操作使棧頂的位址減小,彈出的操作使棧頂位址增大。棧儲存了乙個函式呼叫所需的維護資訊,常常被稱為堆疊幀或者活動記錄。堆疊幀一般包括如下幾個方面:

函式的返回位址和引數。

臨時變數:包括函式的非靜態區域性變數以及編譯器自動生成的其他臨時變數。

儲存的上下文:包括在函式呼叫前後需要保持不變的暫存器。

乙個函式呼叫函式的活動記錄

乙個函式的活動記錄用ebp和esp這兩個暫存器劃定範圍。esp暫存器始終指向棧的頂部,ebp暫存器指向了函式活動記錄的乙個固定位置,ebp暫存器又被稱為幀指標。函式呼叫時棧的操作如下:

把所有的或者一部分引數壓入棧中,如果有其他引數沒有入棧,那麼使用某些特定的暫存器傳遞。

跳轉到函式體執行。

push ebp:把ebp壓入棧中(稱為old ebp)。為了在函式返回的時候便於恢復以前的ebp值。

mov ebp , esp: ebp = esp (這時ebp指向棧頂,而此時棧頂就是old ebp)。

【可選】sub esp,*** :在棧上分配***位元組的臨時空間。

【可選】push ***:如果有必要,儲存名為***的暫存器(可重複多個,由於編譯器可能要求某些暫存器在呼叫前後保持不變,那麼函式就在呼叫開始將這些暫存器的值壓入棧中,在結束後取出)。

函式呼叫結束時

【可選】pop ***:如果有必要,恢復儲存過的暫存器(可重複多個)。

mov esp, ebp:恢復esp同時**區域性變數空間。

pop ebp: 從棧中恢復儲存的ebp的值。

函式返回值傳遞

函式的返回值是通過eax暫存器返回的,但是eax暫存器只有4位元組,如果返回值在5-8位元組範圍內,幾乎所有的呼叫慣例都是採用eax和edx聯合返回的,eax儲存返回值的低4位元組,其他的位元組在edx中儲存。但是大於8位元組的返回值那?

先上**:

在呼叫return_test函式時,進行了如下操作:

首先main函式在棧上額外開闢了一片空間,並將這塊空間的一部分作為傳遞返回值的臨時物件,這裡稱為temp。

將temp物件的位址作為隱藏引數傳遞給return_test函式。

return_test函式將資料拷貝給temp物件,並將temp物件的位址用eax傳出。

return_test 返回之後,main函式將eax指向的temp物件的內容拷貝給n。

也就是如下偽**:

所以我們在程式設計時盡量不要返回大於4位元組的資料,避免兩次拷貝,減小開銷。

02 堆

棧上的資料在函式返回時就會被釋放掉,所以無法將資料傳至函式外部,而全域性變數沒有辦法動態地產生,只能在編譯的時候定義,在這種情況下,堆是唯一地選擇。malloc是c語言申請堆空間的函式,但是它是怎麼實現的那?

其實可以直接讓作業系統的核心來管理程序的記憶體,但是每次申請記憶體都要經過系統呼叫,如果操作頻繁會導致效率很低,程式效能降低。比較好的做法是程式向作業系統申請一塊適當的堆空間,然後由程式的執行庫根據演算法管理堆空間的分配,當堆空間不夠的時候再向作業系統申請堆空間。linux下提供兩種堆空間分配方式:乙個是brk()系統呼叫,另外乙個是mmap()。

int brk (void *end_data_segment)

brk()的作用實際上就是設定程序資料段的結束位址,她可以擴大或者縮小資料段。

void mmap(void *start, size_t length, int port, int flags, int fd, off_t offset)

mmap的前兩個引數分別指定需要申請的空間的起始位址和長度,如果其實位址設為0,那麼作業系統會挑選合適的起始位址。port/flags這兩個引數用於設定申請的空間的許可權(可讀,可寫,可執行)以及對映型別(檔案型別,匿名空間等),最後兩個引數用於檔案對映是指定檔案的描述符和檔案偏移。用mmap實現的malloc函式:

mmap()的作用是向作業系統申請一段虛擬空間,當這塊虛擬空間可以對映到某個檔案(也就是這個系統呼叫的最初的作用),當他不將位址空間對映到某個檔案時,我們又稱這塊空間為匿名空間。

glibc的malloc 函式是這樣處理使用者的空間請求的:對於小於128kb的請求來說,它會在現有的堆空間裡面,按照堆分配演算法為它分配一塊空間返回,對於大於128kb的請求來說,它會使用mmap()函式為它分配一塊匿名空間,然後再這個匿名空間中為使用者分配空間。(所以問乙個很常見的問題,malloc申請的記憶體,程序結束以後還會不會存在? 答案是不存在)

堆分配演算法

1、空閒鍊錶法

空閒鍊錶的方法是把堆中各個空閒的快按照鍊錶的方式連線起來,當使用者請求一塊空間時,可以遍歷整個列表,直到找到合適大小的快並且將它拆分,當使用者釋放空間時將它合併到空閒鍊錶中。

2、位圖

核心思想就是將整個堆劃分為大量的塊,每個塊大小相同。當使用者請求記憶體的時候總是分配整個塊的空間給使用者。第乙個塊我們稱為已分配區域的頭,其餘的稱為已分配區域的主體。而我們可以使用乙個整數陣列來記錄塊的使用情況,由於每個塊只有頭/主體/空閒三種狀態,因此僅僅需要兩位即可表示乙個塊,因此稱為位圖。

優點:速度快,穩定性好,容易管理。

缺點:容易產生碎片,浪費空間。

3 物件池

如果實際上在一些場合,被分配物件的大小是固定的幾個值,我們可以採用物件池的方法。物件池思想就是,如果每一次分配的空間大小都一樣,那麼就可以按照這個每次請求分配的大小作為乙個單位,把整個堆空間劃分為大量的小塊,每次請求只要找到乙個空閒的小塊就可以了。

實際上很多應用中,堆的分配演算法往往是採取多種演算法復合而成的,對於glibc來說,小於64位元組的採用物件池的方法,對於大於512位元組的採用最佳適配演算法,對於64位元組和512位元組之間的採取最佳折中策略;對於大於128kb的申請,它會直接使用mmap向作業系統申請空間。

原文:

記憶體區域型別 暫存器 棧 堆 常量池 非RAM儲存

1.暫存器 最快的儲存區由編譯器根據需求進行分配 2.棧 存放基本型別的變數和物件的引用,物件本身不存放在棧中,而是存放在堆或常量池中 3.堆 存放所有new出來的物件 4.靜態域 存放靜態成員 static定義的 5.常量池 存放字串常量和基本型別常量 public static final 6....

基於verilog的處理器設計之暫存器堆

該暫存器堆是cpu中多個暫存器組成的陣列,由32個32位的暫存器構成,兩個讀資料口 ra busa rb busb 乙個寫資料口 rw busw 寫資料受使能訊號wen控制,在時鐘的下降沿有效 第一種實現方法,如下 module d ff input clk,input 4 0 ra,input 4...

msp430學習筆記2 堆疊指標暫存器 SP詳解

堆疊是一種具有 後進先出 lifo last in first out 特殊訪問屬性的儲存結構。堆疊一般使用ram 物理資源作為儲存體,再加上lifo 訪問介面實現。在隨機儲存器區劃出一塊區域作為堆疊區,資料可以乙個個順序地存入 壓入 到這個區域之中,這個過程稱為 壓棧 或 入棧 push 通常用乙...