Gostaria de compartilhar um experimento de allocator aonde
se pode informar um "destructor" no momento da alocação.
Este destructor é chamado antes do free se não for nulo.
Além disso, ao destruir o allocator toda memória que foi alocada
por ele também é liberada.
Funcionamento: Basicamente ao se alocar memória
se aloca um pouco mais para armazenar o "destructor " e um
ponteiro para linkar as regiões de memórias alocadas.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union header {
struct {
union header* next; /* next block*/
void (*DTOR)(void*); /* destructor */
} s;
long double x; /* force alignment of blocks */
};
struct allocator
{
union header* head;
};
void* alloc(struct allocator* allocator, int nbytes, void (*F)(void*))
{
int nunits = (nbytes + sizeof(union header) - 1) / sizeof(union header) + 1;
union header* p = malloc(nunits * sizeof(union header));
if (p)
{
p->s.next = allocator->head;
p->s.DTOR = F;
allocator->head = p;
return (void*)(p + 1);
}
return NULL;
}
void allocator_destroy(struct allocator* allocator)
{
union header* p = allocator->head;
while (p)
{
union header* next = p->s.next;
if (p->s.DTOR)
{
void* pobj = (void*)((union header*)p + 1);
p->s.DTOR(pobj);
free(p);
}
p = next;
}
}
struct X
{
char* name;
};
void DestroyX(struct X* p)
{
printf("DestroyX");
}
int main()
{
struct allocator allocator = { 0 };
struct X* pX = alloc(&allocator, sizeof(struct X), DestroyX);
if (pX)
{
pX->name = alloc(&allocator, strlen("teste"), NULL);
strcpy(pX->name, "teste");
}
allocator_destroy(&allocator);
}
Uma variante é também permitir free em qualquer momento.
Para isso pode-se usar uma lista duplamente encadeada para
acelerar a remoção do nó.
union header {
struct {
union header* prev; /* next block*/
union header* next; /* next block*/
void (*DTOR)(void*); /* destructor */
} s;
long double x; /* force alignment of blocks */
};
void* alloc(struct allocator* allocator, int nbytes, void (*F)(void*))
{
int nunits = (nbytes + sizeof(union header) - 1) / sizeof(union header) + 1;
union header* p = malloc(nunits * sizeof(union header));
if (p)
{
p->s.prev = NULL;
p->s.next = allocator->head;
p->s.DTOR = F;
if (allocator->head != NULL)
{
allocator->head->s.prev = p;
}
allocator->head = p;
return (void*)(p + 1);
}
return NULL;
}
void allocator_free(struct allocator* allocator, void* p)
{
union header* del = ((union header*)p) - 1;
if (allocator->head == del)
allocator->head = del->s.next;
if (del->s.next != NULL)
del->s.next->s.prev = del->s.prev;
if (del->s.prev != NULL)
del->s.prev->s.next = del->s.next;
if (del->s.DTOR)
{
void* pobj = (void*)((union header*)p + 1);
del->s.DTOR(pobj);
}
free(del);
return;
}
Ainda não estou usando este allocator, mas a motivação
são memórias que tem exatamente o mesmo tempo de vida
e que por algum motivo querem este gerenciamento
genérico.