Estrarre gli allocatori e i deallocatori per ciascun tipo. Data una definizione di tipo
typedef struct foo
{
int x;
double y;
char *z;
} Foo;
creare una funzione allocatore
Foo *createFoo(int x, double y, char *z)
{
Foo *newFoo = NULL;
char *zcpy = copyStr(z);
if (zcpy)
{
newFoo = malloc(sizeof *newFoo);
if (newFoo)
{
newFoo->x = x;
newFoo->y = y;
newFoo->z = zcpy;
}
}
return newFoo;
}
una funzione di copia
Foo *copyFoo(Foo f)
{
Foo *newFoo = createFoo(f.x, f.y, f.z);
return newFoo;
}
e una funzione deallocatore
void destroyFoo(Foo **f)
{
deleteStr(&((*f)->z));
free(*f);
*f = NULL;
}
noti che createFoo()
a sua volta chiama un copyStr()
funzione responsabile dell'allocazione della memoria e della copia del contenuto di una stringa. Si noti inoltre che se copyStr()
non riesce e restituisce un valore NULL, quindi newFoo
non tenterà di allocare memoria e restituire un valore NULL. Allo stesso modo, destroyFoo()
chiamerà una funzione per eliminare la memoria per z prima di liberare il resto della struttura. Infine, destroyFoo()
imposta il valore di f su NULL.
La chiave qui è che l'allocatore e il deallocator delegano la responsabilità ad altre funzioni se gli elementi membri richiedono anche la gestione della memoria. Così, come i tipi di ottenere più complicato, è possibile riutilizzare questi ripartitori in questo modo:
typedef struct bar
{
Foo *f;
Bletch *b;
} Bar;
Bar *createBar(Foo f, Bletch b)
{
Bar *newBar = NULL;
Foo *fcpy = copyFoo(f);
Bletch *bcpy = copyBar(b);
if (fcpy && bcpy)
{
newBar = malloc(sizeof *newBar);
if (newBar)
{
newBar->f = fcpy;
newBar->b = bcpy;
}
}
else
{
free(fcpy);
free(bcpy);
}
return newBar;
}
Bar *copyBar(Bar b)
{
Bar *newBar = createBar(b.f, b.b);
return newBar;
}
void destroyBar(Bar **b)
{
destroyFoo(&((*b)->f));
destroyBletch(&((*b)->b));
free(*b);
*b = NULL;
}
Ovviamente, questo esempio presuppone che i membri non hanno una vita al di fuori dei loro contenitori. Non è sempre così, e dovrai progettare la tua interfaccia di conseguenza. Tuttavia, questo dovrebbe darti un'idea di cosa deve essere fatto.
In questo modo è possibile allocare e deallocare la memoria per gli oggetti in un ordine coerente e ben definito, che rappresenta l'80% della battaglia nella gestione della memoria. L'altro 20% si sta assicurando che ogni allocazione di chiamata sia bilanciata da un deallocator, che è la parte veramente difficile.
modificare
cambiato le chiamate alle funzioni delete*
in modo che io sto passando i tipi giusti.
Questo codificatore C di lungo periodo è passato a C++ e il problema è andato via. –
@Neil Butterworth: O quello o (poltiglia più spesso) il problema è diventato molto peggio. – sharptooth
@sharptooth: Non sono un fan del C++, ma si prende cura di un'intera classe di problemi di gestione della memoria. Lasciando quelli veramente difficili, ovviamente. :-) – JesperE