堆相關數據結構
malloc_chunk
看一下源碼
/*
This struct declaration is misleading (but accurate and necessary).
It declares a "view" into memory allowing access to necessary
fields at known offsets from a given base. See explanation below.
*/
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
/* INTERNAL_SIZE_T is the word-size used for internal bookkeeping of
chunk sizes.
The default version is the same as size_t.
While not strictly necessary, it is best to define this as an
unsigned type, even if size_t is a signed type. This may avoid some
artificial size limitations on some systems.
On a 64-bit machine, you may be able to reduce malloc overhead by
defining INTERNAL_SIZE_T to be a 32 bit `unsigned int' at the
expense of not being able to handle more than 2^32 of malloced
space. If this limitation is acceptable, you are encouraged to set
this unless you are on a platform requiring 16byte alignments. In
this case the alignment requirements turn out to negate any
potential advantages of decreasing size_t word size.
Implementors: Beware of the possible combinations of:
- INTERNAL_SIZE_T might be signed or unsigned, might be 32 or 64 bits,
and might be the same width as int or as long
- size_t might have different width and signedness as INTERNAL_SIZE_T
- int and long might be 32 or 64 bits, and might be the same width
To deal with this, most comparisons and difference computations
among INTERNAL_SIZE_Ts should cast them to unsigned long, being
aware of the fact that casting an unsigned int to a wider long does
not sign-extend. (This also makes checking for negative numbers
awkward.) Some of these casts result in harmless compiler warnings
on some systems. */
#ifndef INTERNAL_SIZE_T
# define INTERNAL_SIZE_T size_t
#endif
/* The corresponding word size. */
#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))
/* The corresponding bit mask value. */
#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
在程序的執行過程中,我們稱由 malloc 申請的內存為 chunk 。這塊內存在 ptmalloc 內部用 malloc_chunk 結構體來表示。當程序申請的 chunk 被 free 后,會被加入到相應的空閑管理列表中。
非常有意思的是,無論一個 chunk 的大小如何,處于分配狀態還是釋放狀態,它們都使用一個統一的結構。雖然它們使用了同一個數據結構,但是根據是否被釋放,它們的表現形式會有所不同。
源碼的每個字段的具體的解釋如下
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
- prev_size, 如果該 chunk 的物理相鄰的前一地址 chunk(兩個指針的地址差值為前一 chunk 大小)是空閑的話,那該字段記錄的是前一個 chunk 的大小 (包括 chunk 頭)。否則,該字段可以用來存儲物理相鄰的前一個 chunk 的數據。這里的前一 chunk 指的是較低地址的 chunk 。
- size ,該 chunk 的大小,大小必須是 2 * SIZE_SZ 的整數倍。如果申請的內存大小不是 2 * SIZE_SZ 的整數倍,會被轉換滿足大小的最小的 2 * SIZE_SZ 的倍數。32 位系統中,SIZE_SZ 是 4;64 位系統中,SIZE_SZ 是 8。 該字段的低三個比特位對 chunk 的大小沒有影響,它們從高到低分別表示
- NON_MAIN_ARENA,記錄當前 chunk 是否不屬于主線程,1 表示不屬于,0 表示屬于。
- IS_MAPPED,記錄當前 chunk 是否是由 mmap 分配的。
- PREV_INUSE,記錄前一個 chunk 塊是否被分配。一般來說,堆中第一個被分配的內存塊的 size 字段的 P 位都會被設置為 1,以便于防止訪問前面的非法內存。當一個 chunk 的 size 的 P 位為 0 時,我們能通過 prev_size 字段來獲取上一個 chunk 的大小以及地址。這也方便進行空閑 chunk 之間的合并。
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
- fd,bk。 chunk 處于分配狀態時,從 fd 字段開始是用戶的數據。chunk 空閑時,會被添加到對應的空閑管理鏈表中,其字段的含義如下
- fd 指向下一個(非物理相鄰)空閑的 chunk
- bk 指向上一個(非物理相鄰)空閑的 chunk
- 通過 fd 和 bk 可以將空閑的 chunk 塊加入到空閑的 chunk 塊鏈表進行統一管理
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
- fd_nextsize, bk_nextsize,也是只有 chunk 空閑的時候才使用,不過其用于較大的 chunk(large chunk)。
- fd_nextsize 指向前一個與當前 chunk 大小不同的第一個空閑塊,不包含 bin 的頭指針。
- bk_nextsize 指向后一個與當前 chunk 大小不同的第一個空閑塊,不包含 bin 的頭指針。
- 一般空閑的 large chunk 在 fd 的遍歷順序中,按照由大到小的順序排列。這樣做可以避免在尋找合適 chunk 時挨個遍歷。
一個已經分配的 chunk 的樣子如下。我們稱前兩個字段稱為 chunk header,后面的部分稱為 user data。每次 malloc 申請得到的內存指針,其實指向 user data 的起始處。
當一個 chunk 處于使用狀態時,它的下一個 chunk 的 prev_size 域無效,所以下一個 chunk 的該部分也可以被當前 chunk 使用。這就是 chunk 中的空間復用。

被釋放的 chunk 被記錄在鏈表中(可能是循環雙向鏈表,也可能是單向鏈表)。具體結構如下

可以發現,如果一個 chunk 處于 free 狀態,那么會有兩個位置記錄其相應的大小
本身的 size 字段會記錄,
它后面的 chunk 會記錄。
一般情況下,物理相鄰的兩個空閑 chunk 會被合并為一個 chunk 。堆管理器會通過 prev_size 字段以及 size 字段合并兩個物理相鄰的空閑 chunk 塊。
參考