J'essaie d'implémenter malloc
et free
pour C, et je ne sais pas comment réutiliser la mémoire. J'ai actuellement un struct
qui ressemble à ceci:
typedef struct _mem_dictionary {
void *addr;
size_t size;
int freed;
} mem_dictionary;
Mon malloc
ressemble à ceci:
void *malloc(size_t size) {
void *return_ptr = sbrk(size);
if (dictionary == NULL)
dictionary = sbrk(1024 * sizeof(mem_dictionary));
dictionary[dictionary_ct].addr = return_ptr;
dictionary[dictionary_ct].size = size;
dictionary[dictionary_ct].freed = 1;
dictionary_ct++;
return return_ptr;
}
Lorsque je libère de la mémoire, je marque simplement l'adresse comme 0
(cela indiquerait qu'il est gratuit). Dans mon malloc
, j'utiliserais alors une boucle for pour rechercher toute valeur du tableau égale à 0
, puis allouez de la mémoire à cette adresse. Je suis un peu confus sur la façon de mettre en œuvre cela.
La façon la plus simple de le faire est de conserver une liste chaînée de blocs libres. Dans malloc
, si la liste n'est pas vide, vous recherchez un bloc suffisamment grand pour satisfaire la demande et le renvoyez. Si la liste est vide ou si aucun bloc de ce type ne peut être trouvé, vous appelez sbrk
pour allouer de la mémoire à partir du système d'exploitation. dans free
, vous ajoutez simplement le morceau de mémoire à la liste des blocs libres. En bonus, vous pouvez essayer de fusionner un bloc libéré contigu, et vous pouvez changer la politique de choix du bloc à retourner (premier ajustement, meilleur ajustement, ...). Vous pouvez également choisir de diviser le bloc s'il est supérieur à la demande.
Quelques exemples d'implémentation (il n'est pas testé et n'est évidemment pas thread-safe, utilisez à vos risques et périls):
typedef struct free_block {
size_t size;
struct free_block* next;
} free_block;
static free_block free_block_list_head = { 0, 0 };
static const size_t overhead = sizeof(size_t);
static const size_t align_to = 16;
void* malloc(size_t size) {
size = (size + sizeof(size_t) + (align_to - 1)) & ~ (align_to - 1);
free_block* block = free_block_list_head.next;
free_block** head = &(free_block_list_head.next);
while (block != 0) {
if (block->size >= size) {
*head = block->next;
return ((char*)block) + sizeof(size_t);
}
head = &(block->next);
block = block->next;
}
block = (free_block*)sbrk(size);
block->size = size;
return ((char*)block) + sizeof(size_t);
}
void free(void* ptr) {
free_block* block = (free_block*)(((char*)ptr) - sizeof(size_t));
block->next = free_block_list_head.next;
free_block_list_head.next = block;
}
Remarque: (n + align_to - 1) & ~ (align_to - 1)
Est une astuce pour arrondir n
au multiple de align_to
Le plus proche de n
. Cela ne fonctionne que lorsque align_to
Est une puissance de deux et dépend de la représentation binaire des nombres.
Lorsque align_to
Est une puissance de deux, il n'a qu'un seul ensemble de bits, et donc align_to - 1
A tous les ensembles de bits les plus faibles (c'est-à-dire que align_to
Est de la forme 000 .. .010 ... 0, et align_to - 1
Est de la forme 000...001...1
). Cela signifie que ~ (align_to - 1)
a tout le bit haut défini et le bit bas non défini (c'est-à-dire qu'il a la forme 111...110...0
). Ainsi, x & ~ (align_to - 1)
mettra à zéro tous les bits bas de x
et l'arrondira au multiple de align_to
Le plus proche.
Enfin, en ajoutant align_to - 1
À size
, assurez-vous que nous arrondissons au multiple de align_to
Le plus proche (sauf si size
est déjà un multiple de align_to
auquel cas nous voulons obtenir size
).
Vous ne voulez pas mettre le champ size
de l'entrée de dictionnaire à zéro - vous aurez besoin de ces informations pour les réutiliser. Au lieu de cela, définissez freed=1
Uniquement lorsque le bloc est libéré.
Vous ne pouvez pas fusionner les blocs adjacents car il peut y avoir eu des appels intermédiaires à sbrk()
, ce qui facilite les choses. Vous avez juste besoin d'une boucle for
qui recherche un bloc libéré suffisamment grand:
typedef struct _mem_dictionary
{
void *addr;
size_t size;
int freed;
} mem_dictionary;
void *malloc(size_t size)
{
void *return_ptr = NULL;
int i;
if (dictionary == NULL) {
dictionary = sbrk(1024 * sizeof(mem_dictionary));
memset(dictionary, 0, 1024 * sizeof(mem_dictionary));
}
for (i = 0; i < dictionary_ct; i++)
if (dictionary[i].size >= size
&& dictionary[i].freed)
{
dictionary[i].freed = 0;
return dictionary[i].addr;
}
return_ptr = sbrk(size);
dictionary[dictionary_ct].addr = return_ptr;
dictionary[dictionary_ct].size = size;
dictionary[dictionary_ct].freed = 0;
dictionary_ct++;
return return_ptr;
}
void free(void *ptr)
{
int i;
if (!dictionary)
return;
for (i = 0; i < dictionary_ct; i++ )
{
if (dictionary[i].addr == ptr)
{
dictionary[i].freed = 1;
return;
}
}
}
Ce n'est pas une excellente implémentation de malloc()
. En fait, la plupart des implémentations malloc
/free
alloueront un petit en-tête pour chaque bloc retourné par malloc. L'en-tête peut commencer à l'adresse huit (8) octets moins que le pointeur renvoyé, par exemple. Dans ces octets, vous pouvez stocker un pointeur vers l'entrée mem_dictionary
Propriétaire du bloc. Cela évite l'opération O(N) dans free
. Vous pouvez éviter l'opération O(N) dans malloc()
en implémentant une file d'attente prioritaire de blocs libérés. Envisagez d'utiliser un binôme tas, avec la taille de bloc comme index.
J'emprunte du code à la réponse de Sylvain. Il semble avoir manqué le calcul de la taille du free_block * dans le calcul des frais généraux.
Dans l'ensemble, le code fonctionne en ajoutant ce free_block en tant qu'en-tête à la mémoire allouée. 1. Lorsque l'utilisateur appelle malloc, malloc renvoie l'adresse de la charge utile, juste après cet en-tête. 2. lorsque free est appelé, l'adresse de début de l'en-tête du bloc est calculée (en soustrayant la taille de l'en-tête de l'adresse du bloc) et est ajoutée au pool de blocs libres.
typedef struct free_block {
size_t size;
struct free_block* next;
} free_block;
static free_block free_block_list_head = { 0, 0 };
// static const size_t overhead = sizeof(size_t);
static const size_t align_to = 16;
void* malloc(size_t size) {
size = (size + sizeof(free_block) + (align_to - 1)) & ~ (align_to - 1);
free_block* block = free_block_list_head.next;
free_block** head = &(free_block_list_head.next);
while (block != 0) {
if (block->size >= size) {
*head = block->next;
return ((char*)block) + sizeof(free_block);
}
head = &(block->next);
block = block->next;
}
block = (free_block*)sbrk(size);
block->size = size;
return ((char*)block) + sizeof(free_block);
}
void free(void* ptr) {
free_block* block = (free_block*)(((char*)ptr) - sizeof(free_block ));
block->next = free_block_list_head.next;
free_block_list_head.next = block;
}