Domanda

Nella mia azienda esiste una regola di codifica che dice che, dopo aver liberato la memoria, reimpostare la variabile su NULL. Ad esempio ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Sento che, in casi come il codice mostrato sopra, l'impostazione su NULL non ha alcun significato. O mi sto perdendo qualcosa?

Se in questi casi non ha senso, lo affronterò con il "team di qualità" per rimuovere questa regola di codifica. Per favore, consigli.

È stato utile?

Soluzione

L'impostazione di puntatori non utilizzati su NULL è uno stile difensivo, che protegge da bug puntatori penzolanti. Se si accede a un puntatore pendente dopo che è stato liberato, è possibile leggere o sovrascrivere la memoria casuale. Se si accede a un puntatore nullo, si ottiene un arresto immediato sulla maggior parte dei sistemi, indicando immediatamente qual è l'errore.

Per le variabili locali, potrebbe essere un po 'inutile se è "ovvio" che il puntatore non è più accessibile dopo essere stato liberato, quindi questo stile è più appropriato per i dati dei membri e le variabili globali. Anche per le variabili locali, può essere un buon approccio se la funzione continua dopo il rilascio della memoria.

Per completare lo stile, è inoltre necessario inizializzare i puntatori su NULL prima che gli venga assegnato un valore di puntatore vero.

Altri suggerimenti

Impostare un puntatore su NULL dopo gratuito è una pratica dubbia che viene spesso resa popolare come "buona programmazione" si pronuncia su una premessa palesemente falsa. È una di quelle false verità che appartengono al "suono giusto" categoria, ma in realtà non ottengono assolutamente nulla di utile (e talvolta portano a conseguenze negative).

Presumibilmente, impostare un puntatore su NULL dopo free dovrebbe impedire il temuto "double free" problema quando lo stesso valore del puntatore viene passato a gratuito più di una volta. In realtà, tuttavia, in 9 casi su 10 il reale "doppio libero" il problema si verifica quando diversi oggetti puntatore con lo stesso valore puntatore vengono utilizzati come argomenti per libero . Inutile dire che, impostando un puntatore su NULL dopo gratuito non si ottiene assolutamente nulla per prevenire il problema in tali casi.

Naturalmente, è possibile imbattersi in " double free " problema quando si utilizza lo stesso oggetto puntatore come argomento per free . Tuttavia, in realtà situazioni come questa normalmente indicano un problema con la struttura logica generale del codice, non un semplice "doppio libero" accidentale. Un modo corretto di affrontare il problema in questi casi è di rivedere e ripensare la struttura del codice per evitare la situazione quando lo stesso puntatore viene passato a free più di una volta. In tali casi, impostare il puntatore su NULL e considerare il problema "risolto" non è altro che un tentativo di spazzare il problema sotto il tappeto. Semplicemente non funzionerà nel caso generale, perché il problema con la struttura del codice troverà sempre un altro modo di manifestarsi.

Infine, se il tuo codice è progettato specificamente per fare affidamento sul valore del puntatore su NULL o meno su NULL , è perfettamente corretto impostare il valore del puntatore su NULL dopo gratuito . Ma come "buona pratica" generale regola (come in " imposta sempre il puntatore su NULL dopo gratuito ") è, ancora una volta, un falso noto e piuttosto inutile, spesso seguito da alcuni per motivi puramente religiosi, simili a vudù.

La maggior parte delle risposte si è concentrata sulla prevenzione di un doppio libero, ma l'impostazione del puntatore su NULL ha un altro vantaggio. Una volta liberato un puntatore, quella memoria è disponibile per essere riallocata da un'altra chiamata a Malloc. Se hai ancora il puntatore originale intorno potresti finire con un bug in cui tenti di usare il puntatore dopo aver liberato e corrotto qualche altra variabile, e quindi il tuo programma entra in uno stato sconosciuto e possono accadere tutti i tipi di cose cattive (crash se sei fortunato, corruzione dei dati se sei sfortunato). Se avessi impostato il puntatore su NULL dopo libero, qualsiasi tentativo di leggere / scrivere attraverso quel puntatore in seguito comporterebbe un segfault, che è generalmente preferibile alla corruzione casuale della memoria.

Per entrambi i motivi, può essere una buona idea impostare il puntatore su NULL dopo free (). Non è sempre necessario, però. Ad esempio, se la variabile del puntatore esce dall'ambito immediatamente dopo free (), non c'è motivo di impostarla su NULL.

Questa è considerata una buona pratica per evitare di sovrascrivere la memoria. Nella funzione sopra, non è necessario, ma spesso quando viene fatto può trovare errori dell'applicazione.

Prova invece qualcosa del genere:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

DEBUG_VERSION ti consente di liberare i profili nel codice di debug, ma entrambi sono funzionalmente uguali.

Modifica : aggiunto do ... mentre come suggerito di seguito, grazie.

Se raggiungi il puntatore che è stato libero () d, potrebbe rompersi o meno. Quella memoria potrebbe essere riassegnata a un'altra parte del tuo programma e quindi otterrai corruzione della memoria,

Se si imposta il puntatore su NULL, quindi se si accede ad esso, il programma si blocca sempre con un segfault. Non più, a volte funziona '', non di più, si blocca in modo imprevedibile ''. È molto più facile eseguire il debug.

L'impostazione del puntatore sulla memoria libera significa che qualsiasi tentativo di accedere a tale memoria tramite il puntatore si bloccherà immediatamente, invece di causare un comportamento indefinito. Rende molto più facile determinare dove le cose sono andate male.

Riesco a vedere il tuo argomento: dal momento che nPtr esce dall'ambito subito dopo nPtr = NULL , non sembra esserci un motivo per impostarlo su < code> NULL . Tuttavia, nel caso di un membro struct o da qualche altra parte in cui il puntatore non esce immediatamente dall'ambito, ha più senso. Non è immediatamente chiaro se quel puntatore sarà usato o meno dal codice che non dovrebbe usarlo.

È probabile che la regola sia dichiarata senza fare una distinzione tra questi due casi, perché è molto più difficile applicare automaticamente la regola, figuriamoci per gli sviluppatori che la seguono. Non fa male impostare i puntatori su NULL dopo ogni libero, ma ha il potenziale di evidenziare grossi problemi.

il bug più comune in c è il doppio libero. Fondamentalmente fai qualcosa del genere

free(foobar);
/* lot of code */
free(foobar);

e finisce piuttosto male, il sistema operativo cerca di liberare un po 'di memoria già liberata e generalmente segfault. Quindi la buona pratica è impostare su NULL , in modo da poter fare test e verificare se è davvero necessario liberare questa memoria

if(foobar != null){
  free(foobar);
}

si noti inoltre che gratuito (NULL) non farà nulla, quindi non è necessario scrivere l'istruzione if. Non sono davvero un guru del sistema operativo, ma sono abbastanza anche ora che la maggior parte dei sistemi operativi si bloccherebbe in doppia libera.

Questo è anche il motivo principale per cui tutte le lingue con Garbage Collection (Java, Dotnet) erano così orgogliose di non avere questo problema e di non dover lasciare agli sviluppatori la gestione della memoria nel suo insieme.

L'idea alla base di questo è di interrompere il riutilizzo accidentale del puntatore liberato.

Questo (può) effettivamente essere importante. Sebbene tu liberi la memoria, una parte successiva del programma potrebbe allocare qualcosa di nuovo che sembra atterrare nello spazio. Il tuo vecchio puntatore ora punta a un pezzo di memoria valido. È quindi possibile che qualcuno utilizzi il puntatore, determinando uno stato del programma non valido.

Se si annulla il puntatore a NULL, qualsiasi tentativo di utilizzarlo farà la dereference 0x0 e andrà in crash proprio lì, il che è facile da eseguire il debug. È difficile eseguire il debug di puntatori casuali che puntano alla memoria casuale. Ovviamente non è necessario ma è per questo che si trova in un documento sulle migliori pratiche.

Dallo standard ANSI C:

void free(void *ptr);
  

La funzione libera causa lo spazio   indicato da ptr per essere deallocato,   cioè, reso disponibile per ulteriori   allocazione. Se ptr è un puntatore nullo,   non si verifica alcuna azione. Altrimenti, se il   l'argomento non corrisponde a un puntatore   precedentemente restituito dal calloc,   funzione malloc o realloc o if   lo spazio è stato deallocato da a   chiama a freeloc o realloc, il comportamento   non è definito.

"il comportamento indefinito" è quasi sempre un arresto anomalo del programma. Per evitare ciò, è sicuro ripristinare il puntatore su NULL. free () stesso non può farlo poiché viene passato solo un puntatore, non un puntatore a un puntatore. Puoi anche scrivere una versione più sicura di free () che NULLs il puntatore:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

Trovo che questo sia di scarso aiuto come nella mia esperienza quando le persone accedono a un'allocazione di memoria liberata è quasi sempre perché hanno un altro puntatore ad esso da qualche parte. E poi è in conflitto con un altro standard di codifica personale che è "Evita disordine inutile", quindi non lo faccio perché penso che raramente aiuta e rende il codice leggermente meno leggibile.

Tuttavia, non imposterò la variabile su null se il puntatore non deve essere riutilizzato, ma spesso il design di livello superiore mi dà un motivo per impostarlo su null comunque. Ad esempio, se il puntatore è un membro di una classe e ho eliminato ciò a cui punta, allora il "contratto" se ti piace della classe, quel membro punterà a qualcosa di valido in qualsiasi momento, quindi deve essere impostato su null per quel motivo. Una piccola distinzione ma penso che sia importante.

In c ++ è importante pensare sempre a chi possiede questi dati quando si alloca un po 'di memoria (a meno che non si utilizzino i puntatori intelligenti ma anche allora è necessario un pensiero). E questo processo tende a far sì che i puntatori siano generalmente membri di una classe e in genere si desidera che una classe sia sempre in uno stato valido e il modo più semplice per farlo è impostare la variabile membro su NULL per indicarla punti a niente adesso.

Un modello comune è impostare tutti i puntatori membri su NULL nel costruttore e fare in modo che la chiamata del distruttore venga eliminata su tutti i puntatori ai dati che il progetto afferma che la classe possiede . Chiaramente in questo caso devi impostare il puntatore su NULL quando elimini qualcosa per indicare che non possiedi alcun dato prima.

Quindi, per riassumere, sì, ho spesso impostato il puntatore su NULL dopo aver eliminato qualcosa, ma fa parte di un progetto più ampio e dei pensieri su chi possiede i dati piuttosto che seguire ciecamente una regola standard di codifica. Non lo farei nel tuo esempio poiché penso che non ci sia alcun vantaggio nel farlo e aggiunge "disordine". che nella mia esperienza è responsabile di bug e codici errati come questo genere di cose.

Di recente mi sono imbattuto nella stessa domanda dopo aver cercato la risposta. Ho raggiunto questa conclusione:

È buona prassi e bisogna seguirla per renderla portatile su tutti i sistemi (integrati).

free () è una funzione di libreria, che varia quando si cambia piattaforma, quindi non ci si deve aspettare che dopo aver passato il puntatore a questa funzione e dopo aver liberato memoria, questo puntatore sarà impostato su NULL . Questo potrebbe non essere il caso di alcune librerie implementate per la piattaforma.

quindi vai sempre per

free(ptr);
ptr = NULL;

Questa regola è utile quando si tenta di evitare i seguenti scenari:

1) Hai una funzione davvero lunga con complicate logiche e gestione della memoria e non vuoi riutilizzare accidentalmente il puntatore nella memoria eliminata più avanti nella funzione.

2) Il puntatore è una variabile membro di una classe che ha un comportamento abbastanza complesso e non si desidera riutilizzare accidentalmente il puntatore nella memoria eliminata in altre funzioni.

Nel tuo scenario, non ha molto senso, ma se la funzione dovesse allungarsi, potrebbe importare.

Potresti sostenere che impostarlo su NULL potrebbe effettivamente mascherare errori logici in seguito, o nel caso in cui pensi che sia valido, continui a bloccarti su NULL, quindi non importa.

In generale, ti consiglierei di impostarlo su NULL quando pensi che sia una buona idea e di non preoccuparti quando pensi che non ne valga la pena. Concentrati invece sulla scrittura di funzioni brevi e classi ben progettate.

Per aggiungere ciò che altri hanno detto, un buon metodo di utilizzo del puntatore è verificare sempre se si tratta di un puntatore valido o meno. Qualcosa del tipo:


if(ptr)
   ptr->CallSomeMethod();

Contrassegnare esplicitamente il puntatore come NULL dopo averlo liberato consente questo tipo di utilizzo in C / C ++.

Questo potrebbe essere più un argomento per inizializzare tutti i puntatori a NULL, ma qualcosa del genere può essere un bug molto subdolo:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

p finisce nello stesso posto nello stack del precedente nPtr , quindi potrebbe contenere ancora un puntatore apparentemente valido. L'assegnazione a * p potrebbe sovrascrivere tutti i tipi di cose non correlate e portare a brutti bug. Soprattutto se il compilatore inizializza le variabili locali con zero in modalità debug, ma non una volta attivate le ottimizzazioni. Quindi le build di debug non mostrano alcun segno del bug mentre le build di rilascio esplodono casualmente ...

Impostare il puntatore che è stato appena liberato su NULL non è obbligatorio ma è una buona pratica. In questo modo, puoi evitare 1) usando un puntino liberato 2) liberalo a rimorchio

Impostazioni che un puntatore a NULL serve a proteggere nuovamente il cosiddetto double-free - una situazione in cui free () viene chiamato più di una volta per lo stesso indirizzo senza riallocare il blocco a quell'indirizzo.

Il doppio libero porta a comportamenti indefiniti - di solito si accumula corruzione o si blocca immediatamente il programma. Chiamare free () per un puntatore NULL non fa nulla ed è quindi garantito per essere sicuro.

Quindi la migliore pratica a meno che tu non sia ora sicuro che il puntatore lasci l'ambito immediatamente o molto presto dopo free () è di impostare quel puntatore su NULL in modo che anche se free () viene chiamato di nuovo, ora viene chiamato per un puntatore NULL e il comportamento indefinito viene eluso.

L'idea è che se provi a dereferenziare il puntatore non più valido dopo averlo liberato, vuoi fallire duramente (segfault) piuttosto che silenziosamente e misteriosamente.

Ma ... stai attento. Non tutti i sistemi causano un segfault se si fa riferimento a NULL. Su (almeno alcune versioni di) AIX, * (int *) 0 == 0 e Solaris ha la compatibilità opzionale con questa funzione "AIX". & Quot;

Alla domanda originale: L'impostazione del puntatore su NULL direttamente dopo aver liberato il contenuto è una completa perdita di tempo, a condizione che il codice soddisfi tutti i requisiti, sia completamente sottoposto a debug e non verrà mai più modificato. D'altra parte, NULL difensivamente un puntatore che è stato liberato può essere molto utile quando qualcuno aggiunge senza pensarci un nuovo blocco di codice sotto il free (), quando il design del modulo originale non è corretto, e nel caso di esso -compiles-but-not-do-what-I-want bugs.

In qualsiasi sistema, c'è un obiettivo irraggiungibile di rendere più semplice la cosa giusta e il costo irriducibile di misurazioni imprecise. In C ci viene offerto un set di strumenti molto affilati e molto potenti, che possono creare molte cose nelle mani di un lavoratore specializzato e infliggere ogni sorta di lesioni metaforiche se maneggiate in modo improprio. Alcuni sono difficili da capire o usare correttamente. E le persone, essendo naturalmente avverse al rischio, fanno cose irrazionali come controllare un puntatore per valore NULL prima di chiamare gratis con esso ...

Il problema della misurazione è che ogni volta che si tenta di dividere il bene dal meno buono, più complesso è il caso, più è probabile che si ottenga una misurazione ambigua. Se l'obiettivo è mantenere solo le buone pratiche, allora alcune ambigue vengono buttate fuori con il non buono. Se il tuo obiettivo è eliminare il non buono, allora le ambiguità potrebbero rimanere con il bene. I due obiettivi, mantenere solo il bene o eliminare chiaramente il male, sembrerebbero essere diametralmente opposti, ma di solito c'è un terzo gruppo che non è né l'uno né l'altro, alcuni di entrambi.

Prima di presentare un caso con il dipartimento qualità, prova a consultare la banca dati dei bug per vedere con quale frequenza, se mai, valori del puntatore non validi causano problemi che devono essere scritti. Se vuoi fare la vera differenza, identifica il problema più comune nel tuo codice di produzione e proponi tre modi per prevenirlo

Esistono due motivi:

Evita arresti anomali durante il doppio libering

Scritto da RageZ in un domanda duplicata .

  

Il bug più comune in c è il doppio   gratuito. Fondamentalmente fai qualcosa del genere   che

free(foobar);
/* lot of code */
free(foobar);
     

e finisce piuttosto male, prova il sistema operativo   per liberare un po 'di memoria già liberata e   generalmente segfault. Quindi il buono   la pratica è impostare su NULL , quindi tu   può fare test e verificare se davvero   è necessario liberare questa memoria

if(foobar != NULL){
  free(foobar);
}
     

si noti inoltre che gratuito (NULL)   non farà nulla, quindi non è necessario   scrivi l'istruzione if. io non sono   davvero un guru del sistema operativo, ma io sono abbastanza uniforme   ora la maggior parte dei sistemi operativi andrebbero in crash su doppio   gratuito.

     

Questo è anche il motivo principale per cui tutti   lingue con garbage collection   (Java, dotnet) era così orgoglioso di no   avendo questo problema e anche no   dover lasciare allo sviluppatore il   gestione della memoria nel suo insieme.

Evita l'uso di puntatori già liberati

Scritto da Martin v. L & # 246; wis in a un'altra risposta .

  

L'impostazione di puntatori non utilizzati su NULL è a   stile difensivo, protezione contro   bug puntatore penzoloni. Se penzoloni   si accede al puntatore dopo che è stato liberato,   puoi leggere o sovrascrivere in modo casuale   memoria. Se si accede a un puntatore null,   si ottiene un arresto immediato sulla maggior parte   sistemi, dirti subito cosa   l'errore è.

     

Per le variabili locali, potrebbe essere a   un po 'inutile se lo è   & Quot; ovvio " che il puntatore non lo è   più accessibile dopo essere stato liberato, quindi   questo stile è più appropriato per   dati dei membri e variabili globali. Anche   per le variabili locali, potrebbe essere un bene   approccio se la funzione continua   dopo il rilascio della memoria.

     

Per completare lo stile, dovresti anche   inizializza i puntatori su NULL prima   gli viene assegnato un puntatore vero   valore.

Dato che è in atto un team di controllo della qualità, vorrei aggiungere un punto minore sul QA. Alcuni strumenti di QA automatizzati per C contrassegneranno le assegnazioni ai puntatori liberati come "assegnazione inutile a ptr " ;. Ad esempio, afferma PC-lint / FlexeLint di Gimpel Software tst.c 8 Avviso 438: ultimo valore assegnato alla variabile 'nPtr' (definito alla riga 5) non utilizzato

Esistono modi per sopprimere i messaggi in modo selettivo, quindi puoi comunque soddisfare entrambi i requisiti di QA, se il tuo team lo decide.

È sempre consigliabile dichiarare una variabile puntatore con NULL come,

int *ptr = NULL;

Diciamo che ptr punta a 0x1000 indirizzo di memoria. Dopo aver usato free (ptr) , è sempre consigliabile annullare la variabile puntatore dichiarando nuovamente su NULL . per esempio:.

free(ptr);
ptr = NULL;

Se non dichiarato nuovamente su NULL , la variabile puntatore continua a puntare allo stesso indirizzo ( 0x1000 ), questa variabile puntatore viene chiamata penzoloni puntatore . Se definisci un'altra variabile di puntatore (diciamo, q ) e assegni dinamicamente l'indirizzo al nuovo puntatore, c'è la possibilità di prendere lo stesso indirizzo ( 0x1000 ) con un nuovo puntatore variabile. Se nel caso, usi lo stesso puntatore ( ptr ) e aggiorni il valore all'indirizzo indicato dallo stesso puntatore ( ptr ), il programma finirà per scrivere un valore al punto in cui q punta (poiché p e q puntano allo stesso indirizzo ( 0x1000 ) ).

per es.

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.

Per farla breve: non si desidera accedere accidentalmente (per errore) all'indirizzo che si è liberato. Perché, quando si libera l'indirizzo, si consente a tale indirizzo nell'heap di essere assegnato a un'altra applicazione.

Tuttavia, se non si imposta il puntatore su NULL e si tenta per sbaglio di de-referenziare il puntatore o modificare il valore di quell'indirizzo; PUOI ANCORA Farlo. MA QUALCOSA CHE VUOI FARE LOGICAMENTE.

Perché posso ancora accedere alla posizione di memoria che ho liberato? Perché: è possibile che la memoria sia libera, ma la variabile del puntatore conteneva ancora informazioni sull'indirizzo di memoria dell'heap. Quindi, come strategia difensiva, impostalo su NULL.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top