這是我去騰訊面試的時候遇到的一個問題——malloc()是如何申請內存的?c++
c++ 內存獲取和釋放 new/delete,new[]/delete[]面試
c 內存獲取和釋放 malloc/free, calloc/realloc數組
上述8個函數/操做符是c/c++語言裏經常使用來作動態內存的申請和釋放的,要理解這些接口,大概須要安全
下面幾個維度的瞭解:cookie
1. 瞭解OS的進程空間模型,一個進程的地址空間,通常劃分爲內核區、用戶區,用戶區又劃分爲棧區、堆區、數據區、代碼區。函數
這裏的‘堆區’,‘棧區’,‘數據區’,‘內核區’,其實就是一個虛擬地址區間,動態內存最終都是從OS的'堆區'上獲取的。google
2. brk、mmap 系統調用指針
brk系統調用,可讓進程的堆指針增加必定的大小,邏輯上消耗掉一塊本進程的虛擬地址區間,malloc向OS獲取的內存大小比較小時,將直接經過brk調用獲取虛擬地址,結果是將本進程的brk指針推高。對象
mmap系統調用,可讓進程的虛擬地址區間裏切分出一塊指定大小的虛擬地址區間vma_struct,並返回給用戶態進程,被mmap映射返回的虛擬地址,邏輯上被消耗了,直到用戶進程調用munmap,纔回收回來。malloc向系統獲取比較大的內存時,會經過mmap直接映射一塊虛擬地址區間。mmap系統調用用處很是多,好比一個進程的全部動態庫文件.so的加載,都須要經過mmap系統調用映射指定大小的虛擬地址區間,而後將.so代碼動態映射到這些區域,以供進程其餘部分代碼訪問;另外,多進程通信,也可使用mmap,這塊另開文章詳解。繼承
不管是brk仍是mmap返回的都是虛擬地址,在第一次訪問這塊地址的時候,會觸發缺頁異常,而後內核爲這塊虛擬地址申請並映射物理頁框,創建頁表映射關係,後續對該區間虛擬地址的訪問,經過頁表獲取物理地址,而後就能夠在物理內存上讀寫了。
3. malloc/free 是libc庫函數
malloc/free是 libc實現的庫函數,主要實現了一套內存管理機制,當其管理的內存不夠時,經過brk/mmap等系統調用向內核申請進程的虛擬地址區間,若是其維護的內存能知足malloc調用,則直接返回,free時會將地址塊返回空閒鏈表。
malloc(size) 的時候,這個函數會多分配一塊空間,用於保存size變量,free的時候,直接經過指針前移必定大小,就能夠獲取malloc時保存的size變量,從而free只須要一個指針做爲參數就能夠了calloc 庫函數至關於 malloc + memset(0)
除了libc自帶的動態內存管理庫malloc, 有時候還可使用其餘的內存管理庫替換,好比使用google實現的tcmalloc ,只須要編譯進程時連接上 tcmalloc的靜態庫幷包含響應頭文件,就能夠透明地使用tcmalloc 了,與libc 的malloc相比, tcmalloc 在內存管理上有不少改進,效率和安全性更好。
4. new/new[]/delete/delete[]
new/delete 是c++ 內置的運算符,至關於加強版的malloc/free. c++是兼容c的,通常來講,一樣功能的庫,c++會在安全性和功能性方面
比c庫作更多工做。動態內存管理這塊也同樣。
new的實現會調用malloc,對於基本類型變量,它只是增長了一個cookie結構, 好比須要new的對象大小是 object_size, 則事實上調用 malloc 的參數是 object_size + cookie, 這個cookie 結構存放的信息包括對象大小,對象先後會包含兩個用於檢測內存溢出的變量,全部new申請的cookie塊會連接成雙向鏈表。因爲內置了內存溢出檢測,因此比malloc更安全。
對於自定義類型,new會先申請上述的大小空間,而後調用自定義類型的構造函數,對object所在空間進行構造。c++比c強大的一個方面
就是c++編譯器能夠自動作構造和析構,new運算符會自動計算須要的空間大小,而後根據類型本身調用構造函數,若是存在子類型對象,或者存在繼承的基類型,new都會自動調用子類型的構造函數和基類型的構造函數完成構造。一樣,delete 操做符根據cookie的size知道object的大小,若是是自定義類型,會調用析構函數對object所在空間進行析構,若是有子類型或繼承,自動調用子類型和基類型的析構函數,而後將cookie塊從雙向鏈表摘除,最後調用 free_dbg 釋放。
new[] 和delete[]是另外兩個操做符,用於數組類型的動態內存獲取和釋放,實現過程相似new/delete