8-Маллоклаб
Добро пожаловать на авторский блог сакура без крашеной груши одежда
Этот эксперимент следует рассматривать как самый сложный, с перекрытием различных указателей, но это также классический эксперимент, такой как бомбероб.
1 Краткий анализ
Цель эксперимента: Ознакомиться с указателями языка C, ознакомиться со способом применения динамической памяти и структурой динамической памяти.
Рабочая область: mm.c
Содержание эксперимента: напишите динамический распределитель памяти для реализации операций init, malloc, realloc, free и других, а также максимально оптимизируйте этот распределитель, чтобы сбалансировать пропускную способность и использование пространства.
Экспериментальный инструмент: инструмент mdriver, который может определять правильность
2 Конкретная реализация
1. Структура кучи
Куча — это область виртуальной памяти процесса. Первый взгляд на структуру кучи
Адрес кучи непрерывен, и вся область памяти является кучей.Так как же управлять всей этой областью памяти. Есть три распространенных способа
- Неявный свободный список:
- Разделите всю кучу на множество соседних блоков.Заголовок каждого блока содержит информацию о размере блока и о том, выделен ли он.По размеру этого блока мы можем найти начало следующего блока, связанного с ним.адрес
- Преимущества: Простой
- Недостатки: каждое приложение нужно проходить с нуля, пропускная способность низкая, легко генерируется множество фрагментов пространства.
- Явные бесплатные списки:
- Улучшение неявного списка свободных: на основе неявного списка свободных каждый свободный блок не только сохраняет свой размер и информацию о том, выделен ли он, но также сохраняет указатель на следующий и/или предыдущий свободный блок, так что при просмотре для свободных блоков вам нужно только пройти по списку свободных блоков
- Преимущества: повышена эффективность распределения памяти и уменьшена фрагментация пространства.
- Недостатки: требует много места для хранения указателей, сложно поддерживать,
- Отдельные бесплатные списки:
- Поддерживайте несколько списков свободных, блоки в каждом списке примерно равны по размеру, а распределитель поддерживает массив списков свободных.
- Преимущества: повышение эффективности распределения памяти и уменьшение фрагментации пространства.
- Недостатки: сложно обслуживать, может привести к низкому использованию пространства.
Наша цель: сбалансировать пропускную способность и использование пространства (эти два факта фактически конфликтуют. Невозможно иметь высокую пропускную способность и высокий коэффициент использования пространства, потому что высокая пропускная способность должна использовать такие структуры, как связанные списки, хеш-таблицы, деревья, и т. д. Эти конструкции неизбежно приведут к снижению использования пространства, поэтому должен быть баланс)
2. Блочная структура
Взгляните на структуру блоков, чтобы облегчить понимание кода.
3. Реализация
Обратите внимание, что все этапы кода взяты из CSAPP | Lab8-Malloc Lab углубленного анализа — Zhihu (zhihu.com) , босс слишком крутой, я кратко объясню это здесь.
- Во-первых, определите группу макросов, с которыми легко работать. Я думаю, что эта группа макросов — гениальный ход. Роль макросов очень ясна, поэтому я не буду ее объяснять.
/* 头部/脚部大小 单字(四字节) */
#define WSIZE 4
/* 双字 */
#define DSIZE 8
/* 取最大值 */
#define MAX(a, b) ((a) > (b) ? (a) : (b))
/* 扩展堆时默认大小 */
#define CHUNKSIZE (1 << 12)
/* 设置头部/脚部值 */
#define PACK(size, alloc) ((size)|(alloc))
/* 读写指针p */
/*
首先知道void*类型的指针可以指向任何地方,其实malloc函数申请的指针就是void*类型
(unsigned int *)(p)的意思是将void*类型的指针p强转为指向unsigned int类型的指针
然后(*(unsigned int *)(p))是取出该指针指向的unsigned int类型的值
*/
#define GET(p) (*(unsigned int *)(p))
#define PUT(p, val) ((*(unsigned int *)(p)) = (val))
/* 从头部或脚部获取大小或分配位 */
/* 这里0x是十六进制嗷,低一位表示是否已分配 */
#define GET_SIZE(p) (GET(p) & ~0x7)
#define GET_ALLOC(p) (GET(p) & 0x1)
/* 给定有效载荷指针,找到头部和脚部 */
#define HDRP(bp) ((char *)(bp) - WSIZE)
#define FTRP(bp) ((char *)(bp) + GET_SIZE(HDRP(bp)) - DSIZE)
/* 给定有效载荷指针, 找到前一块或下一块 */
#define NEXT_BLKP(bp) ((char*)(bp) + GET_SIZE(((char*)(bp) - WSIZE)))
#define PREV_BLKP(bp) ((char*)(bp) - GET_SIZE(((char*)(bp) - DSIZE)))
- Затем определите функции, которые вам нужно использовать. Каждая функция прокомментирована здесь.
/**
* @brief 初始化堆
* 初始化一个堆,主要是定义了堆的序言块和结尾块,相当于开头和结尾,然后添加了一个CHUNKSIZE/WSIZE大小的空 间
*/
int mm_init(void);
/**
* @brief 扩展堆
* 用于扩展堆,每当需要申请动态内存的时候就调用该函数来申请动态内存空间
*
* @param words 申请的动态内存大小
*/
void *extend_heap(size_t words);
/**
* @brief 释放内存
*
* @param ptr 需要释放的块指针
*/
void mm_free(void *ptr);
/**
* @brief 分割空闲块
* 判断剩下的空间是否足够存放头部和脚部,够的话就设置各种标志位,不够的话就要设置为填充的块
*
* @param bp 当前空闲块指针
* @param asize 当前请求的块大小
*/
void place(void *bp, size_t asize);
/**
* @brief 合并块
* 合并块有四种情况,需要把相邻的空闲块合并在一起
*
* @param bp 需要合并的块指针
*/
void *coalesce(void *bp);
/**
* @brief 首次适配原则
*
* @param asize 请求的块大小
*/
void *first_fit(size_t asize);
/**
* @brief 自主实现的malloc
*
* @param size 申请的块大小
*/
void *mm_malloc(size_t size);
/**
* @brief 重新申请块大小
*
* @param ptr 重新申请的块的指针
* @param size 重新申请的块大小
*/
void *mm_realloc(void *ptr, size_t size);
Далее идет код каждой части.Комментарии, данные шефом по каждой функции в этой части, очень подробные.Я не буду слишком уродливым, поэтому можете читать медленно.
Резюме: Наконец-то более пикантный. Хотя последний эксперимент был более водянистым, в конце концов он был завершен. Будьте полны энергии с этого момента~