Newsgroup: it.comp.lang.c
Titolo: Deallocare una struttura in una funzione.

Ciao,
avevo un dubbio su una funzione.
io ho una struttura del tipo
typedef struct strutt{
    int * puntatore;
    int ** puntatore2;
} struttura;
che alloco con una funzione
void initStruttura(struttura * s){
   s->puntatore = calloc(3, sizeof(int));
   s->puntatore2 = calloc(2, sizeof(int*));
   s->puntatore2[0] = calloc(2, sizeof(int));
   s->puntatore2[1] = calloc(2, sizeof(int));
}
nel main ho queste chiamate:
   struttura newStruttura;
   initStruttura(&newStruttura);
il dubbio sorge quando voglio deallocare la memoria:
void deInit (struttura * s){
    free(s->puntatore);
    free(s->puntatore2[0]);
    free(s->puntatore2[1]);
    free(s->puntatore2);
}
la chiamata a:
deInit(&newStruttura);
mi libera la memoria occupata in precedenza?
Grazie.
RObbiz

 mi libera la memoria occupata in precedenza?
Per il poco che ne capisco (non sono un esperto di C) credo che il tuo 
codice funzioni correttamente e non causi alcuna "perdita" di memoria, 
in altre parole, sì, ti libera tutta la memoria occupata in precedenza.
Così a naso mi verrebbe da dire che l'ordine "migliore" per quelle 
deallocazioni sia l'esatto inverso dell'ordine di allocazione, quindi:
     free(s->puntatore2[1]);
     free(s->puntatore2[0]);
     free(s->puntatore2);
     free(s->puntatore);
Ma anche il tuo ordine dovrebbe andare bene - l'unica cosa veramente 
importante dovrebbe essere deallocare puntatore2[1] e puntatore2[0] 
prima di puntatore2, e lo stai già facendo correttamente, credo.
Questo codice dovrebbe fornirti lo stesso spazio di memoria (sette int) 
per la struct senza bisogno di allocazioni dinamiche:
typedef struct strutt{
     int i[3];
     int i2[2][2];
} struttura;
Ma se hai chiesto esplicitamente su calloc e free avrai i tuoi motivi e 
sarai di sicuro a conoscenza delle differenze e similitudini tra i due 
costrutti.
Se ho scritto cretinate siete pregati di correggermi! Ne voglio 
approfittare per capire se sto afferrando bene questa storia delle 
allocazioni dinamiche in C.
-- 
  FSC - 
http://userscripts.org/scripts/show/59948
 
http://fscode.altervista.org
 - 
http://sardinias.com


sì, è tutto a posto: io avrei utilizzato un metodo diverso, ma è
questione di gusti.
Giusto due consigli, anche questi opinabili:
- quando allochi con malloc() e famiglia, utilizza il sizeof() della
variabile stessa: s = malloc(sizeof(*s)); in questo modo se cambi il
tipo di s non devi cambiare tutte le allocazioni.
- prima di deallocare, controlla che il valore non sia NULL, e dopo la
deallocazione imposta il puntatore a NULL. In questo modo elimini
fastidiose deallocazioni doppie sullo stesso oggetto.
- cerca di non lasciare "numeretti" in giro (come 3 o 2 nelle
allocazioni), visto che poi dovrai riutilizzarli nei for o nei
controlli di overbound. Usa #define-s oppure array statici se già sai
che non cambieranno a runtime, più sizeof()s.
- controlla che effettivamente le allocazioni siano andate a buon fine
(con poche e piccole non ha effetivamente molto senso, ma è comunque
una buona abitudine).
Ciao!



Scusa se approfondisco su questo punto, non voglio "rubarti" il thread, 
robbiz, voglio solo chiarirmi le idee e sapere se c'è qualche problema 
con il programma che posto qua sotto.
La differenza tra l'approccio di "automatica" e quello di "dinamica" 
(l'approccio di robbiz) dovrebbe essere che la struct "automatica" è 
"fissa", può rappresentare l'accesso solo a quei sette int, mentre la 
struct "dinamica" permette di estendere a piacere sia l'array "diretto" 
(p) che quello "indiretto" (p2).
#include <stdio.h>
typedef struct {
     int* p;
     int** p2;
} dinamica;
void Init(dinamica* d) {
     d->p = calloc(3, sizeof(int));
     d->p2 = calloc(2, sizeof(int*));
     d->p2[0] = calloc(2, sizeof(int));
     d->p2[1] = calloc(2, sizeof(int));
}
void Deinit(dinamica* d) {
     free(d->p2[1]);
     free(d->p2[0]);
     free(d->p2);
     free(d->p);
}
typedef struct {
     int i[3];
     int i2[2][2];
} automatica;
int main(void) {
     dinamica d;
     Init(&d);
     d.p[0] = 1;
     d.p[1] = 2;
     d.p[2] = 3;
     d.p2[0][0] = 4;
     d.p2[0][1] = 5;
     d.p2[1][0] = 6;
     d.p2[1][1] = 7;
     printf("%d %d %d %d %d %d %d\n",
            d.p[0],
            d.p[1],
            d.p[2],
            d.p2[0][0],
            d.p2[0][1],
            d.p2[1][0],
            d.p2[1][1]);
     Deinit(&d);
     automatica s = {{10, 20, 30}, {{40, 50}, {60, 70}}};
     printf("%d %d %d %d %d %d %d\n",
            s.i[0],
            s.i[1],
            s.i[2],
            s.i2[0][0],
            s.i2[0][1],
            s.i2[1][0],
            s.i2[1][1]);
     return 0;


 fastidiose deallocazioni doppie sullo stesso oggetto.
Scusa se mi intrometto, ma questo suggerimento non mi č chiaro.
Deallocare due volte lo stesso oggetto dovrebbe essere segno di qualche 
problema nella struttura del programma, con il tuo metodo tale problema 
viene sistematicamente nascosto, mentre magari (se uno č fortunato) con 
una doppia deallocazione il programmatore si becca un bel crash (ripeto, 
se č fortunato) e quindi puň andare a controllare la struttura logica 
del suo programma per vedere cosa c'č che non va... cosa mi sono perso?



Avere un feedback di una doppia deallocazione è piacevole per il
programmatore, non per il cliente :-)
Se, come programmatore, vuoi che il tuo programma vada in crash in
casi simili, basta usare una macro:
#ifdef NDEBUG
# define MY_FREE(_PTR) do { if((_PTR)!=NULL) free(_PTR);
(_PTR)=NULL; } while(0)
#else
# define MY_FREE(_PTR) free(_PTR)
#endif
Ciao!

 #endif
Giustissimo, grazie per il chiarimento.


 Ciao!
Ciao,
Che metodo avresti utilizzato?
il mio programma è un'esempio di uno più complesso, il fatto
principale è che con deInit(&struttura), ho deallocato tutta la
struttura, ma se dopo questa chiamata accedo a struttura.variabile
(nel mio caso s.puntatore[0]), il programma non mi crasha, come
farebbe il altri casi, sbaglio ad interpretare la free() o sbaglio a
deallocare?
Se fosse la prima come faccio a far consumare meno memoria al mio
programma?
Per tutte le altre considerazioni sono perfettamente d'accordo con te!
Grazie,
robbiz


Il "crash" è uno dei tanti possibili effetti collaterali, non è
assolutamente detto che si verifichi. (OT: comunemente su una
deallocazione la memoria deallocata viene marcata dal sistema
operativo come "libera" per il processo, e non viene ovviamente
"azzerrata", quindi rileggerla "subito dopo" potrebbe andare bene.
Alcuni CRT di debug marcano sulla free() la memoria in maniera
particolare, in maniera che sia possibile accorgersi _anche_ di questi
casi /OT).
Come avrei fatto io? Io solitamente per una struttura "pippo"
definisco una funzione "pippo *pippo_create(....)" e una "void
pippo_destroy(pippo**)". La prima alloca la struttura e ritorna il
puntatore (o NULL in caso di errori), la seconda prende il puntatore
al puntatore della struttura e (se non è NULL) dealloca tutto e
imposta a NULL.
Come al solito non è tanto importante la struttura che si utilizza
(ogni metodo ha i suoi pro e contro), l'importante è che tutti siano
coerenti in ogni parte del codice!
Ciao!

 mi libera la memoria occupata in precedenza?
Ciao,
un modo per ricordarsi dei blocchi allocati è di liberarli in ordine
inverso rispetto a quello di allocazione.
Questo è un piccolo esempio su cui potresti basarti, molto elementare,
ma dà almeno un'idea...
Se non avessi posto a NULL i puntatori ai blocchi di memoria allocati,
dopo averli deallocati, l'ultima printf nel main avrebbe stampato un
numero intero casuale, senza invocare Segmentation fault, e probabil-
mente non ci si accorgerebbe dell'accesso a un'area di memoria non più
'nostra'.
PS: ricorda che passare il nome di un array allocato dinamicamente
     a sizeof non dà la dimensione in byte dell'array (come avviene
     nel caso di array statici), ma semplicemente la dimensione di
     quella variabile puntatore.
#include <stdio.h>
#include <stdlib.h>
typedef int TYPE_TO_ALLOC;
typedef struct strutt {
     TYPE_TO_ALLOC * puntatore;
     TYPE_TO_ALLOC ** puntatore2;
} struttura;
struttura* initStruttura(struttura *s) {
     if( !(s->puntatore = calloc(3, sizeof (TYPE_TO_ALLOC))) )
         return NULL;
     if( !(s->puntatore2 = calloc(2, sizeof (TYPE_TO_ALLOC *))) ) {
         free(s->puntatore);
         return NULL;
     }
     if( !(s->puntatore2[0] = calloc(2, sizeof (TYPE_TO_ALLOC))) ) {
         free(s->puntatore2);
         free(s->puntatore);
         return NULL;
     }
     if( !(s->puntatore2[1] = calloc(2, sizeof (TYPE_TO_ALLOC))) ) {
         free(s->puntatore2[0]);
         free(s->puntatore2);
         free(s->puntatore);
         return NULL;
     }
     return s;
}
void deInit(struttura *s) {
     if( !s->puntatore2[1] ) {
         printf("Attenzione, si dealloca un puntatore NULL, \
                 free(NULL) non ha alcun effetto,\n\
                 memory leak in agguato!\n");
     }
     free(s->puntatore2[1]);
     s->puntatore2[1] = NULL;
     if( !s->puntatore2[0] ) {
         printf("Attenzione, si dealloca un puntatore NULL, \
                 free(NULL) non ha alcun effetto,\n\
                 memory leak in agguato!\n");
     }
     free(s->puntatore2[0]);
     s->puntatore2[0] = NULL;
     if( !s->puntatore2 ) {
         printf("Attenzione, si dealloca un puntatore NULL, \
                 free(NULL) non ha alcun effetto,\n\
                 memory leak in agguato!\n");
     }
     free(s->puntatore2);
     s->puntatore2 = NULL;
     if( !s->puntatore ) {
         printf("Attenzione, si dealloca un puntatore NULL, \
                 free(NULL) non ha alcun effetto,\n\
                 memory leak in agguato!\n");
     }
     free(s->puntatore);
     s->puntatore = NULL;
}
int main(void) {
     struttura newStruttura;
     int a = 33, b = 44, c = 55, d = 66;
     if ( !initStruttura(&newStruttura) )
         printf("Impossibile allocare la struttura\n");
     *newStruttura.puntatore = 11;
     printf("%d\n", *newStruttura.puntatore);
     *newStruttura.puntatore2[0] = a;
     printf("%d\n", *newStruttura.puntatore2[0]);
     *(newStruttura.puntatore2[0] + 1) = b;
     printf("%d\n", *(newStruttura.puntatore2[0] + 1));
     *newStruttura.puntatore2[1] = c;
     printf("%d\n", *newStruttura.puntatore2[1]);
     *(newStruttura.puntatore2[1] + 1) = d;
     printf("%d\n", *(newStruttura.puntatore2[1] + 1));
     deInit(&newStruttura);
     /* Segmentation fault */
     printf("%d\n", *newStruttura.puntatore);
     return 0;
}
-- 
Non puoi insegnare qualcosa ad un uomo. You cannot teach a man anything.
Lo puoi solo aiutare   --  Galileo  Galilei  --   You can only help him
a scoprirla dentro di sé.                        discover it in himself.
Vincenzo Mercuri

 printf("Impossibile allocare la struttura\n");
if ( !initStruttura(&newStruttura) ) {
         printf("Impossibile allocare la struttura\n");
         exit(1);
     }
sennò non sono molto coerente...


 fastidiose deallocazioni doppie sullo stesso oggetto.
Scusa, non comprendo questo punto. Lo standard garantisce che se p ==
NULL, facendo free(p) "no action occurs", che lo interpreto come "e'
equivalente ad una NOP".
Sono invece d'accordo sul settare a NULL un puntatore deallocato.
-- 
-riko
http://www.enrico-franchi.org/
http://rik0-techtemple.blogspot.com/

 Sono invece d'accordo sul settare a NULL un puntatore deallocato.
All'inizio anch'io avevo capito come te. Ma reinterpretando la
sua frase, quel che voleva dire è che bisogna verificare che il punta-
tore non sia NULL prima di deallocare, proprio perchè free(NULL) non
ha alcun effetto. Quindi quella memoria nello heap che era puntata,
prima che il puntatore diventasse NULL, va persa per sempre (finchè
il programma non termina).


 il programma non termina).
Il "Quindi" si deve intendere come "Altrimenti, ", pardon.



Questo scusami ma non l'ho capito io :-O
Ciao!

 Questo scusami ma non l'ho capito io :-O
Cambia il "Quindi" con "Altrimenti", come avevo già suggerito.
Beh fatti due conti, se facciamo diventare NULL un puntatore che prima
puntava a dei blocchi di memoria, e poi eseguiamo free(NULL), cosa 
succede? Anzi, cosa non succede? ;-)


 ragione Enrico, il controllo non è *indispensabile*.
PS: cosa vuol dire "formalmente ha ragione Enrico"?
Lui non ha sostenuto che il controllo non sia *indispensabile*,
ha detto solo che free(NULL) è una NOP. Chi ha sostenuto il contrario? :-o



Da come ha scritto, significa che scrivere:
if (p!=NULL) { free(p); p=NULL; }
e
free(p); p=NULL;
è equivalente, quindi il controllo (per l'argomento di cui stavamo
parlando) non serve.
Ciao!

 parlando) non serve.
Pienamente d'accordo!



wrote:
[...]
Un ulteriore domanda:
Io alloco ammettiamo 200 MB di short int, perchè se stampo prima e
dopo il deInit(&variabile), il comando vmstat non vedo che ho liberato
la memoria?
Grazie.
RobbiZ


Questa è un po' OT ma rispondo uguale. Tieni presente che queste son
cose che cambiano da sistema a sistema, per cui non fartene un'idea
generale... Comunque.. intuitivamente perché il sistema non ha
(ancora) bisogno della memoria: effettuare operazioni aggiuntive
quando non c'è bisogno sarebbe molto stupido da parte del kernel.
Oltretutto a seconda del sistema la memoria (espressa in pagine
virtuali, non fisiche) non verrebbe più smontata dal processo. Se vuoi
fare una prova, fai la seguente: alloca i 200Mb, dealloca, guarda le
statistiche di sistema, ri-alloca i 200Mb.
Comunque qui si entra nel mondo della gestione della memoria da parte
del sistema, che con il C non ha molto a che fare...
Ciao!

 Io alloco ammettiamo 200 MB di short int, perchè se stampo prima e
[...]
Allocare 200 MB richiede un po' di attenzione, specie se hai intenzione
di farlo in maniera portabile. Puoi usare qualcosa del genere, avendo
un'implementazione conforme al C99 (ho volutamente saltato i controlli
su malloc...e tutto il resto):
#include <stdlib.h>
#include <stdint.h>
int main(void) {
#if SIZE_MAX > 65535
     size_t quanti_short = 209715200UL / sizeof(short int);
     short int *p;
         p = malloc(quanti_short * sizeof(short int));
#else
     size_t fattore_short = 51200 / sizeof(short int);
     size_t quante_malloc = 209715200UL / 51200;
  /* [...] */
#endif
     /*
     free(...);
     */



Scusa l'OT ma volevo capire se era un problema di codice (che mi
riguarda da vicino) o di gestione della memoria dell'OS o di
svuotamento del buffer stdout... Ma mia hai risposto in maniera
adeguata: finchè il sistema non ne chiede di nuova, la memoria resta
di quel processo.
Mi interessava sapere che il mio codice non fosse sbagliato, è
perfettibilissimo, ma non sbagliato!!! :D
Grazie!
Robbiz