深入分析malloc

2021-08-04 08:00:52 字數 3096 閱讀 9056

本文大量參考了如何實現乙個malloc這篇文章。

任何乙個用過或學過c的人對malloc都不會陌生。大家都知道malloc可以分配一段連續的記憶體空間,並且在不再使用時可以通過free釋放掉。但是,許多程式設計師對malloc背後的事情並不熟悉,許多人甚至把malloc當做作業系統所提供的系統呼叫或c的關鍵字。實際上,malloc只是c的標準庫中提供的乙個普通函式,而且實現malloc的基本思想並不複雜,任何乙個對c和作業系統有些許了解的程式設計師都可以很容易理解。

根據標準c庫函式的定義,malloc具有如下原型:

void* malloc(size_t size);
這個函式要實現的功能是在系統中分配一段連續的可用的記憶體,具體有如下要求:

本人在windows開發場景中遇到了一類崩潰問題,某個內存在release庫中分配,在debug庫中釋放,引發了崩潰,所以引發了了解malloc原理的想法。

c語言的標準記憶體分配函式包括以下函式:

malloc,calloc,realloc,free等。

這些函式的調式版本為:

malloc_dbg,calloc_dbg,realloc_dbg, free_dbg等。

malloc分配資料時開闢了兩個記憶體,meta區和資料區,資料區為我們使用的資料儲存區,meta區一般包含資料大小/是否釋放/下一資料區指標等內容。malloc_dbg 分配記憶體時儲存有關記憶體分配的資訊,如在什麼檔案、哪一行分配的記憶體等。所以malloc和malloc_dbg分配的記憶體大小是不一致的。

為了簡單,現代作業系統在處理記憶體位址時,普遍採用虛擬記憶體位址技術。即在匯程式設計序(或機器語言)層面,當涉及記憶體位址時,都是使用虛擬記憶體位址。採用這種技術時,每個程序彷彿自己獨享一片2n

位元組的記憶體,其中n是機器位數。例如在64位cpu和64位作業系統下,每個程序的虛擬位址空間為264

byte。

這種虛擬位址空間的作用主要是簡化程式的編寫及方便作業系統對程序間記憶體的隔離管理,真實中的程序不太可能(也用不到)如此大的記憶體空間,實際能用到的記憶體取決於物理記憶體大小。

由於在機器語言層面都是採用虛擬位址,當實際的機器碼程式涉及到記憶體操作時,需要根據當前程序執行的實際上下文將虛擬位址轉換為物理記憶體位址,才能實現對真實記憶體資料的操作。這個轉換一般由乙個叫mmu(memory management unit)的硬體完成。

在現代作業系統中,不論是虛擬記憶體還是物理記憶體,都不是以位元組為單位進行管理的,而是以頁(page)為單位。乙個記憶體頁是一段固定大小的連續記憶體位址的總稱,具體到linux中,典型的記憶體頁大小為4096byte(4k)。

上面是虛擬記憶體位址,下面是物理記憶體位址。由於頁大小都是4k,所以頁內便宜都是用低12位表示,而剩下的高位址表示頁號。

mmu對映單位並不是位元組,而是頁,這個對映通過查乙個常駐記憶體的資料結構頁表來實現。現在計算機具體的記憶體位址對映比較複雜,為了加快速度會引入一系列快取和優化,例如tlb等機制。下面給出乙個經過簡化的記憶體位址翻譯示意圖,雖然經過了簡化,但是基本原理與現代計算機真實的情況的一致的。

我們知道一般將記憶體看做磁碟的的快取,有時mmu在工作時,會發現頁表表明某個記憶體頁不在物理記憶體中,此時會觸發乙個缺頁異常(page fault),此時系統會到磁碟中相應的地方將磁碟頁載入到記憶體中,然後重新執行由於缺頁而失敗的機器指令。關於這部分,因為可以看做對malloc實現是透明的,所以不再詳細講述,有興趣的可以參考《深入理解計算機系統》相關章節。

明白了虛擬記憶體和物理記憶體的關係及相關的對映機制,下面看一下具體在乙個程序內是如何排布記憶體的。

以linux 64位系統為例。理論上,64bit記憶體位址可用空間為0x0000000000000000 ~ 0xffffffffffffffff,這是個相當龐大的空間,linux實際上只用了其中一小部分(256t)。

根據linux核心相關文件描述,linux64位作業系統僅使用低47位,高17位做擴充套件(只能是全0或全1)。所以,實際用到的位址為空間為0x0000000000000000 ~ 0x00007fffffffffff和0xffff800000000000 ~ 0xffffffffffffffff,其中前面為使用者空間(user space),後者為核心空間(kernel space)。圖示如下:

對使用者來說,主要關注的空間是user space。將user space放大後,可以看到裡面主要分為如下幾段:

data:這裡存放的是初始化過的全域性變數

bss:這裡存放的是未初始化的全域性變數

下面我們主要關注heap區域的操作。對整個linux記憶體排布有興趣的同學可以參考其它資料。

一般來說,malloc所申請的記憶體主要從heap區域分配(本文不考慮通過mmap申請大塊記憶體的情況)。

linux維護乙個break指標,這個指標指向堆空間的某個位址。從堆起始位址到break之間的位址空間為對映好的,可以供程序訪問;而從break往上,是未對映的位址空間,如果訪問這段空間則程式會報錯。

int brk(void *addr);

void *sbrk(intptr_t increment);

brk將break指標直接設定為某個位址,而sbrk將break從當前位置移動increment所指定的增量。brk在執行成功時返回0,否則返回-1並設定errno為enomem;sbrk成功時返回break移動之前所指向的位址,否則返回(void *)-1。

乙個小技巧是,如果將increment設定為0,則可以獲得當前break的位址。

另外需要注意的是,由於linux是按頁進行記憶體對映的,所以如果break被設定為沒有按頁大小對齊,則系統實際上會在最後對映乙個完整的頁,從而實際已對映的記憶體空間比break指向的地方要大一些。但是使用break之後的位址是很危險的(儘管也許break之後確實有一小塊可用記憶體位址)。

系統對每乙個程序所分配的資源不是無限的,包括可對映的記憶體空間,因此每個程序有乙個rlimit表示當前程序可用的資源上限。這個限制可以通過getrlimit系統呼叫得到,下面**獲取當前程序虛擬記憶體空間的rlimit:

int main()
其中rlimit是乙個結構體:

struct rlimit ;
每種資源有軟限制和硬限制,並且可以通過setrlimit對rlimit進行有條件設定。其中硬限制作為軟限制的上限,非特權程序只能設定軟限制,且不能超過硬限制。

malloc和new的深入分析

1.malloc 函式 1.1 malloc的全稱是memory allocation,中文叫動態記憶體分配。原型 extern void malloc unsigned int num bytes 說明 分配長度為num bytes位元組的記憶體塊。如果分配成功則返回指向被分配記憶體的指標,分配失...

IsPostBack深入分析

1 ispostback 介紹 ispostback是 page類有乙個 bool型別的屬性,用來判斷針對當前 form的請求是第一次還是非第一次請求。當 ispostback true時表示非第一次請求,我們稱為 postback,當 ispostback false時表示第一次請求。在 asp....

深入分析ConcurrentHashMap

再多執行緒的情況下,如果使用hashmap,就會導致死迴圈,導致cpu利用率接近100 所以如果是併發的情況不要使用hashmap 導致死迴圈主要是這段 當在多執行緒的情況由於沒有同步導致,著段 在擴容的時候會執行 do while e null 執行緒安全的hashtable 容器 hashtab...