Я решил не использовать кучу в своей программе и создать собственный диспетчер памяти для извлечения фрагментов памяти из большого глобального массива uint8_t (u8). Я выбрал u8, так как 8 бит — это байт. Очень большой размер MEMORY_BLOCK_SIZE не является проблемой, поскольку в сборках Release компилятор удаляет неиспользуемые байты. Я отслеживаю свободные участки памяти с помощью того, что я называю «поиском».
#include <assert.h>
// NOTE: null is a redefinition of NULL
// NOTE: u8 = uint8_t
// NOTE: u64 = uint64_t
#define MEMORY_BLOCK_SIZE 8388608
#define MEMORY_LOOKUPS 8
static u8 memoryBlock[ MEMORY_BLOCK_SIZE ];
static u8* memoryLookups[ MEMORY_LOOKUPS ];
static u64 memoryLookupsSizes[ MEMORY_LOOKUPS ];
void memoryInit( void )
{
memoryLookups[ 0 ] = memoryBlock;
memoryLookupsSizes[ 0 ] = MEMORY_BLOCK_SIZE;
}
u8* requestMemoryChunk( u64 size )
{
assert( size );
u8* usablePtr = null;
u64 ptrPosition;
for( u64 i = MEMORY_LOOKUPS; i >= 0; --i )
{
if( ( !( memoryLookups[ i ] ) ) &&
( memoryLookupsSizes[ i ] >= size ) )
{
usablePtr = memoryLookups[ i ];
ptrPosition = i;
break;
}
}
assert( usablePtr );
u8* block = usablePtr;
memoryLookupsSizes[ ptrPosition ] -= size;
if( !( memoryLookupsSizes[ ptrPosition ] ) )
memoryLookups[ ptrPosition ] = null;
else
memoryLookups[ ptrPosition ] = usablePtr + size;
return block;
}
void freeMemoryChunk( u64 size, u8* block )
{
assert( block && size );
for( u64 i = 0; i < MEMORY_LOOKUPS; ++i )
{
if( ! ( memoryLookups[ i ] ) )
{
memoryLookups[ i ] = block;
memoryLookupsSizes[ i ] = size;
break;
}
}
}
```
2 ответа
Пожалуйста, не переименовывайте NULL, uint8_t и uint64_t. Это делает ваш код очень трудным для чтения. Если вы действительно не хотите вводить все это, выполните поиск и замену после того, как закончите кодирование.
Хорошо известно, что проверка if (! X) такая же, как if (x == NULL) и if (x == 0). Но в конечном итоге я предпочитаю явные версии для удобства чтения.
Также
for(int x=0; x< MEMORY_LOOKUPS; x++) {
uint8_t* x = requestMemoryChunk(MEMORY_BLOCK_SIZE - 1 - x);
freeMemoryChunk(x, MEMORY_BLOCK_SIZE - 1 - x);
}
uint8_t* y = requestMemoryChunk(2);
// you just lost 99.99% of your memory
Если этот пример слишком экстремальный, рассмотрите
for(int x=0; x< MEMORY_LOOKUPS; x++) {
uint8_t* x = requestMemoryChunk(10000 - x);
freeMemoryChunk(x, 10000 - x);
}
uint8_t* y = requestMemoryChunk(2);
// you just lost ~10000 bytes of memory
Рассмотрите возможность привязки запросов к размерам блоков (например, степени двойки), чтобы избежать подобных патологических запросов. Далее рассмотрите возможность сортировки поисковых запросов по размеру.
Стилистически все в порядке. В условиях if есть ненужные круглые скобки, но (… вы знаете, есть лисп)
В конечном итоге люди пишут на c для эффективности и контроля. Кто-то ожидает потерять читабельность, делая умные вещи. Но нет причин не объяснять умные вещи в комментариях.
Вы не можете реализовать такую систему диспетчера памяти в стандарте C. Он должен полагаться на стандартные расширения и определенные параметры компилятора. Основная причина этого — «строгое правило алиасинга» … (Что такое строгое правило псевдонима?)
… но также и выравнивание, вам не следует писать библиотеку, которая распределяет неверно выровненные фрагменты памяти. Если вы посмотрите на malloc и друзей, они работают только потому, что у них есть требование (из стандарта C 7.22.3):
Указатель, возвращаемый в случае успешного выделения, выравнивается соответствующим образом, чтобы его можно было назначить указателю на любой тип объекта с фундаментальным требованием выравнивания, а затем использовать для доступа к такому объекту или массиву таких объектов в выделенном пространстве (до пространство явно освобождено).
Как отмечалось в другом обзоре, вам не следует придумывать самодельные секретные псевдонимы для стандартных терминов. Использовать
stdint.h
с участиемuint8_t
и т. д. ИспользованиеNULL
. В частности, не переопределяйтеNULL
к чему-то нестандартному, поскольку это может вызвать всевозможные тонкие ошибки. Видеть В чем разница между нулевыми указателями и NULL? для подробностей.Ошибка здесь:
for( u64 i = MEMORY_LOOKUPS; i >= 0; --i )
Anu64
всегда> = 0. Приличные компиляторы должны вас тут предупредить. В общем, подобных ошибок можно избежать, всегда выполняя итерацию от 0 и выше, а затем вместо этого меняя индексацию:arr[MAX - i]
и т.п.Как правило, код качества библиотеки не проверяет, являются ли переданные параметры NULL, 0 и т. Д. Такие вещи обрабатываются документацией, и вызывающая сторона несет ответственность за то, чтобы не передавать параметры мусора.
Ваши подпрограммы должны отслеживать последнее использованное место в памяти, а не искать его во время выполнения. Я полагаю, что некоторые из них могут быть освобождены, поэтому в справочной таблице будут пробелы, но тогда вы должны спросить себя, действительно ли массив является правильным классом контейнера для использования. Вы можете, например, использовать вместо этого BST, отсортированный по адресу памяти.
Ваш код, похоже, вообще не решает проблему фрагментации.
Очень большой размер MEMORY_BLOCK_SIZE не является проблемой, поскольку в сборках Release компилятор удаляет неиспользуемые байты.
Я бы не был так уверен в этом, это во многом зависит от системы и контекста.
Спасибо за обзор, я согласен со всеми пунктами, которые вы упомянули. Я думал, что u64 / u8 / null нетрудно читать, так как это довольно распространенная практика программирования на переменном языке. Чтобы решить проблему нерационального использования памяти, вам следует изменить MEMORY_LOOKUPS в соответствии с потребностями вашей программы. (то же, что и MEMORY_BLOCK_SIZE). Я стараюсь делать это как можно меньше, потому что чем больше поисков программа проверяет, тем больше итераций она должна сделать.
— рано