Итак, я никогда раньше не слышал термин «выдвижной распределитель», но концепция проста.
Это действительно наивный распределитель, который может быть очень быстрым из-за небольшого количества операций по обслуживанию, но у вас есть жить с довольно тяжелым ограничением: нет "бесплатной" операции для отдельного запроса - вы просто уничтожаете все.
В вашем случае вы звоните sbrk(0)
, чтобы получить первый адрес в конце всего сегмента данных программы - это будет возвращаемое значение - затем "увеличьте" конец памяти с помощью sbrk(nbytes)
после соответствующего округления в большую сторону.
Это означает, что пространство данных программы просто увеличивается для каждого запрос, и попытка освободить что-то не имеет никакого смысла, потому что вы не можете просто сделать дыру в адресном пространстве (ну, вероятно, есть какие-то забавные штуки с виртуальными машинами, которые будут работать, но это усложняется).
static void *bump_arena_start = 0;
void* my_malloc(size_t size) {
void* p = sbrk(0);
if (bump_arena_start == 0) bump_arena_start = p;
if (sbrk(align(size)) == (void*) -1)
return NULL; // failed
return p;
}
void destroy_bump_arena(void)
{
if (bump_arena_start) brk(bump_arena_start);
}
Это, вероятно, был бы выделитель с задержкой, но это был бы ужасный по целому ряду причин.
Пихта st: предполагается, что никто другой не выделяет память, ему придется переопределить все другие операции: malloc
, C++ new
, все во время выполнения C, et c.
Представьте, что произойдет, если вы сделаете свое дело с перерывом, а затем некоторые другие вызовы функций sbrk()
для выделения некоторой памяти. Теперь они находятся в центре вашей арены, но вы в основном этого не знаете. Пока нет проблем, но как только вы go уничтожаете свою арену, это убивает все остальное.
Фактически вы можете использовать такую вещь, когда у вас есть много крошечных выделений, которые вы не хотите отслеживать и можете выпускать все сразу, поэтому вы можете использовать системный распределитель (malloc()
) и запросить достаточно большой кусок для удовлетворения ваших потребностей - назовем его 32kbytes - и вставьте это в некоторые объект, представляющий эту арену выступов.
Вы выделяете много маленьких битов здесь и там, выполняете любую задачу, которая вам нужна, а затем уничтожаете все это, освобождая этот начальный 32-килобайтный кусок.
Дело в том, что вы должны быть очень осторожны , чтобы не позволить одному из этих указателей уйти в другие части системы, потому что им не разрешено жить за пределами время жизни вашей арены.
Это просто действительно специализированный вариант использования, который, вероятно, в целом бесполезен, и, если вы не выполняете встроенную работу (где вы, по сути, контролируете свою собственную среду выполнения), вы действительно не могли бы сделать один с системой ломается таким образом.
Примечание: у вас могут возникнуть проблемы с выравниванием, если у вас есть объекты, превышающие размер целочисленного указателя. Что, если бы вы сделали это?
int *foo1 = my_malloc(sizeof(int)); // 8 bytes (usually)
__int128 *foo2 = my_malloc(sizeof(__int128)); // 16 bytes
Наивное выравнивание поместило бы int на 8-байтовую границу, но также и 128-битное значение (которое составляет 16 байтов), не выровненное по собственному размеру ; на некоторых платформах это, вероятно, ошибка, и это почти всегда неэффективно.
Чтобы сделать это правильно, вы должны запросить текущее следующее значение через sbrk(0)
и убедиться, что это было правильно выровнено для размер, возможно, немного увеличив разрыв.
РЕДАКТИРОВАТЬ : Я изучил это немного больше, и довольно ясно, что ваш пример не совсем считается распределителем ударов . Вот почему.
Система памяти не только отслеживает «следующий» указатель, но и сколько выделений было выполнено, и поддерживает псевдосвободную операцию, которая игнорирует значение указателя, а просто уменьшает счетчик выделения .
Если счетчик распределения когда-либо достигает нуля, это означает, что ни у кого больше нет этой памяти, поэтому он может освободить все, перемотав разрыв до начального значения, по сути, начиная с чистого листа.
Чтобы использовать это правильно, вы должны быть очень осторожны с освобождением мест, а двойное освобождение может быть очень болезненным.
Действительно полезная ссылка: https://os.phil-opp.com/allocator-designs/
EDIT2 - подробнее о выравнивании по запросу.
Вы должны иметь хоть какое-то представление о выравнивании, независимо от того, на какой платформе вы находитесь, потому что даже если платформа разрешает невыровненный доступ к памяти, он почти всегда медленнее.
Супер простой способ всегда Чтобы понять это правильно, необходимо определить максимально возможный скалярный объект, поддерживаемый на платформе, и использовать его в качестве вашего выравнивания по модулю, возможно, __int128
. Если вы всегда округляете до ближайших 16 байтов, вы почти никогда не столкнетесь с проблемой выравнивания (плюс это легко).
двухбайтовый short
, он потратит 14 байтов после него. Это может не иметь большого значения для вашего приложения, или это может быть что-то.
Я никогда не писал распределитель памяти, поэтому я здесь много размахиваю руками, но любой, кто использует распределитель ударов, имеет некоторые специализированные требования и, вероятно, подходит для выполнения специализированных запросов. 1078 *, что требуемое выравнивание, сохраните это как возвращаемое значение, затем вызовите sbrk(size)
, чтобы поднять конечный маркер.
Обратите внимание, что вы не выравниваете размер выделения, но только размер элемента нижнего уровня: запрос массива из 20 short
значений означает, что вы запрашиваете 40 байтов, но с выравниванием 2, а 100 байтов для строки означает, что вы просто берете следующие 100 байтов w / o любое выравнивание.
void *my_malloc(size_t nbytes, size_t align = 8)
{
void *p = sbrk(0);
p += round up but too hard to think on Friday
sbrk(nbytes);
num_allocations++;
return p;
}
Таким образом, если вы не укажете размер выравнивания, будет сделано то же безопасное предположение, что и вы, но вы всегда можете спросить, вы хотели быть особенными.
Опять же: я в основном просто выдумываю, мне никогда не приходилось думать об этом таким образом, но я знаю, что если я работаю над ограниченным объемом памяти Платформа, такая как Arduino с RAM, измеряемой в килобайтах , вы должны подумать об этом.