Impostazione variabile su NULL dopo libero


156

Nella mia azienda esiste una regola di codifica che dice che, dopo aver liberato la memoria, reimpostare la variabile su NULL. Per 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 NULLnon ha alcun significato. Oppure mi sfugge qualcosa?

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


3
è sempre utile poter verificare se ptr == NULLprima di fare qualcosa con esso. Se non annulli i puntatori gratuiti, otterrai un puntatore ptr != NULLancora inutilizzabile.
Ki Jéy,

I puntatori ciondolanti possono portare a vulnerabilità sfruttabili come Use-After-Free .
Константин Ван,

Risposte:


285

L'impostazione di puntatori inutilizzati su NULL è uno stile difensivo, che protegge da bug puntatori sospesi. 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.


3
Non capisco perché dovresti "inizializzare i puntatori su NULL prima che gli venga assegnato un valore di puntatore vero"?
Paul Biggar,

26
@Paul: nel caso specifico, la dichiarazione potrebbe essere letta int *nPtr=NULL;. Ora, sarei d'accordo che questo sarebbe ridondante, con un malloc che segue proprio nella riga successiva. Tuttavia, se esiste un codice tra la dichiarazione e la prima inizializzazione, qualcuno potrebbe iniziare a utilizzare la variabile anche se non ha ancora valore. Se si inizializza null, si ottiene il segfault; senza, potresti nuovamente leggere o scrivere memoria casuale. Allo stesso modo, se la variabile in seguito viene inizializzata solo in modo condizionale, accessi errati in seguito dovrebbero causare arresti anomali istantanei se si ricorda di inizializzare null.
Martin v. Löwis,

1
Personalmente penso che in qualsiasi base di codice nulla di banale ottenere un errore per il dereferenziamento di null sia vago quanto ottenere un errore nel dereferenziare un indirizzo che non si possiede. Personalmente non mi preoccupo mai.
Wilhelmtell,

9
Wilhelm, il punto è che con una dereferenza del puntatore nulla si ottiene un determinato arresto e la posizione effettiva del problema. Un accesso errato può causare arresti anomali o meno e danneggiare dati o comportamenti in modi imprevisti in luoghi imprevisti.
Amit Naidu,

4
In realtà, l'inizializzazione del puntatore su NULL presenta almeno uno svantaggio significativo: può impedire al compilatore di avvisarti di variabili non inizializzate. A meno che la logica del codice non gestisca effettivamente esplicitamente quel valore per il puntatore (cioè se (nPtr == NULL) dosomething ...) è meglio lasciarlo così com'è.
Eric,

37

Impostare un puntatore su NULLafter freeè una pratica dubbia che viene spesso resa popolare come regola della "buona programmazione" su una premessa palesemente falsa. È una di quelle false verità che appartengono alla categoria dei "suoni giusti" ma in realtà non ottengono assolutamente nulla di utile (e talvolta portano a conseguenze negative).

Presumibilmente, l'impostazione di un puntatore su NULLafter freedovrebbe impedire il temuto problema "double free" quando lo stesso valore del puntatore viene passato a freepiù di una volta. In realtà, tuttavia, in 9 casi su 10 il vero problema del "doppio libero" si verifica quando vengono utilizzati come argomenti oggetti puntatore diversi che contengono lo stesso valore di puntatore free. Inutile dire che impostare un puntatore su NULLafter non freeraggiunge assolutamente nulla per prevenire il problema in questi casi.

Naturalmente, è possibile imbattersi in un problema "double free" 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 al fine di evitare la situazione quando lo stesso puntatore viene passato a freepiù di una volta. In tali casi, impostare il puntatore su NULLe 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 codice è specificamente progettato per fare affidamento sul valore del puntatore NULLo meno NULL, è perfettamente corretto impostare il valore del puntatore su NULLafter free. Ma come regola generale di "buone pratiche" (come in "imposta sempre il puntatore su NULLdopo free") è, ancora una volta, un falso ben noto e piuttosto inutile, spesso seguito da alcuni per motivi puramente religiosi e voodoo.


1
Decisamente. Non ricordo di aver mai causato una doppia liberazione che sarebbe stata risolta impostando il puntatore su NULL dopo la liberazione, ma ne ho causate molte che non lo avrebbero fatto.
LnxPrgr3,

4
@AnT "dubbia" è un po 'troppo. Tutto dipende dal caso d'uso. Se il valore del puntatore viene mai utilizzato in un senso vero / falso, non è solo una pratica valida, è una buona pratica.
Coder

1
@Coder Completamente sbagliato. Se il valore del puntatore viene utilizzato in un vero falso senso per sapere se punta o meno a un oggetto prima che la chiamata sia libera, non è solo la migliore pratica, ma è sbagliata . Ad esempio: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. Qui, impostando barsu NULLdopo la chiamata free, la funzione penserà che non abbia mai avuto una barra e restituirà un valore errato!
David Schwartz,

Non penso che il vantaggio principale sia la protezione da un doppio libero, piuttosto è quello di catturare puntatori penzolanti prima e in modo più affidabile. Ad esempio, quando si libera una struttura che contiene risorse, puntatori alla memoria allocata, handle di file, ecc., Mentre libero i puntatori di memoria contenuti e chiudo i file contenuti, I NULL membri rispettivi. Quindi, se si accede a una delle risorse tramite un puntatore penzolante per errore, il programma tende a guastarsi proprio lì, ogni volta. Altrimenti, senza NULLing, i dati liberati potrebbero non essere ancora sovrascritti e il bug potrebbe non essere facilmente riproducibile.
Jimhark,

1
Sono d'accordo che un codice ben strutturato non dovrebbe consentire il caso in cui si accede a un puntatore dopo essere stato liberato o il caso in cui è stato liberato due volte. Ma nel mondo reale il mio codice sarà modificato e / o gestito da qualcuno che probabilmente non mi conosce e non ha il tempo e / o le competenze per fare le cose correttamente (perché la scadenza è sempre ieri). Pertanto tendo a scrivere funzioni antiproiettile che non causano l'arresto anomalo del sistema anche se utilizzate in modo improprio.
mfloris,

35

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 il puntatore fosse stato impostato 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.


1
+1 Questo è in realtà un ottimo punto. Non il ragionamento sul "doppio libero" (che è completamente falso), ma questo . Non sono più un fan del NULL meccanico dei puntatori free, ma questo ha davvero senso.
An

Se potessi accedere a un puntatore dopo averlo liberato tramite quello stesso puntatore, è ancora più probabile che tu accedessi a un puntatore dopo aver liberato l'oggetto a cui punta attraverso un altro puntatore. Quindi questo non ti aiuta affatto - devi ancora usare qualche altro meccanismo per assicurarti di non accedere a un oggetto attraverso un puntatore dopo averlo liberato attraverso un altro. Puoi anche usare quel metodo per proteggere anche nello stesso caso puntatore.
David Schwartz,

1
@DavidSchwartz: non sono d'accordo con il tuo commento. Quando ho dovuto scrivere una pila per un esercizio universitario alcune settimane fa, ho avuto un problema, ho studiato alcune ore. Ho avuto accesso ad un po 'di memoria già libera ad un certo punto (la libera era alcune righe troppo presto). E a volte porta a comportamenti molto strani. Se avessi impostato il puntatore su NULL dopo averlo liberato, ci sarebbe stato un "semplice" segfault e avrei risparmiato un paio d'ore di lavoro. Quindi +1 per questa risposta!
Mozzbozz,

2
@katze_sonne Anche un orologio fermo è giusto due volte al giorno. È molto più probabile che impostando i puntatori su NULL si nascondano i bug impedendo agli accessi errati agli oggetti già liberati di segfaultare nel codice che controlla NULL e quindi non controlla silenziosamente un oggetto che avrebbe dovuto controllare. (Forse impostare i puntatori su NULL dopo averli liberati in build di debug specifiche potrebbe essere utile, oppure impostarli su un valore diverso da NULL che è garantito da segfault potrebbe avere senso. Ma che questa sciocchezza è accaduta per aiutarti una volta non è un argomento a suo favore .)
David Schwartz,

@DavidSchwartz Bene, sembra ragionevole ... Grazie per il tuo commento, lo terrò in considerazione in futuro! :) +1
mozzbozz

20

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_tmp = (p); free(*(p_tmp)); *(p_tmp) = 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 suggerito di seguito, grazie.


3
La versione macro ha un bug sottile se la usi dopo un'istruzione if senza parentesi.
Mark Ransom,

Cosa c'è con il (vuoto) 0? Questo codice fa: if (x) myfree (& x); else do_foo (); diventa if (x) {free (* (& x)); * (& x) = null; } vuoto 0; else do_foo (); L'altro è un errore.
jmucchiello,

Quella macro è un posto perfetto per l'operatore virgola: free ( (p)), * (p) = null. Naturalmente il prossimo problema è che valuta * (p) due volte. Dovrebbe essere {void * _pp = (p); Spedizione gratuita); * _pp = null; } Il preprocessore non è divertente.
jmucchiello,

5
La macro non dovrebbe essere tra parentesi spoglie, dovrebbe essere in un do { } while(0)blocco in modo che if(x) myfree(x); else dostuff();non si rompa.
Chris Lutz,

3
Come ha detto Lutz, il macro body do {X} while (0)è l'IMO il modo migliore per fare di una macro un body che "si sente e funziona come". La maggior parte dei compilatori ottimizza comunque il ciclo.
Mike Clark,

7

Se raggiungi il puntatore che è stato libero () d, potrebbe rompersi o meno. Quella memoria potrebbe essere riallocata in 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.


5
Il programma non si arresta sempre in modo anomalo con un segfault. Se il modo in cui accedi al puntatore significa che viene applicato un offset abbastanza grande prima di dereferenziare, allora può raggiungere la memoria indirizzabile: ((MyHugeStruct *) 0) -> fieldNearTheEnd. E questo è ancora prima che tu abbia a che fare con hardware che non segfault sull'accesso 0 affatto. Tuttavia, è più probabile che il programma si blocchi con un segfault.
Steve Jessop,

7

Impostando il puntatore sulla free'd memory' significa che qualsiasi tentativo di accedere a quella 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 la tua argomentazione: dal momento che nPtrsta andando fuori campo subito dopo nPtr = NULL, non sembra esserci un motivo per impostarlo NULL. Tuttavia, nel caso di un structmembro 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 di seguirla. Non fa male impostare i puntatori NULLdopo ogni libero, ma ha il potenziale di evidenziare grossi problemi.


7

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 è quella di impostare NULL, in modo da poter fare test e verificare se è davvero necessario liberare questa memoria

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

anche da notare che free(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.


11
In realtà puoi semplicemente chiamare free () senza selezionare - free (NULL) è definito come non fare nulla.
Ambra

5
Non nascondono bug? (Mi piace liberare troppo.)
Georg Schölly il

1
grazie, ho capito. ho provato:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang il

5
Come ho detto, free(void *ptr) non è possibile modificare il valore del puntatore che viene passato. Può cambiare il contenuto del puntatore, i dati memorizzati a quell'indirizzo , ma non l' indirizzo stesso o il valore del puntatore . Ciò richiederebbe free(void **ptr)(che a quanto pare non è consentito dallo standard) o una macro (che è consentita e perfettamente portatile ma alle persone non piacciono le macro). Inoltre, C non è per comodità, è per dare ai programmatori tutto il controllo che vogliono. Se non vogliono l'overhead aggiuntivo dell'impostazione dei puntatori NULL, non dovrebbe essere forzato su di essi.
Chris Lutz,

2
Ci sono poche cose al mondo che danno via la mancanza di professionalità da parte dell'autore del codice C. Ma includono "il controllo del puntatore per NULL prima di chiamare free" (insieme a "casting del risultato delle funzioni di allocazione della memoria" o "uso sconsiderato di nomi di tipo con sizeof").
An

6

L'idea alla base di ciò è quella di interrompere il riutilizzo accidentale del puntatore liberato.


4

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 zero, 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.


Su Windows, almeno, le build di debug imposteranno la memoria su 0xdddddddd, quindi quando si utilizza un puntatore per eliminare la memoria che si conosce immediatamente. Dovrebbero esserci meccanismi simili su tutte le piattaforme.
i_am_jorf,

2
jeffamaphone, il blocco di memoria cancellato potrebbe essere stato riallocato e assegnato a un altro oggetto quando si utilizza nuovamente il puntatore.
Constantin,

4

Dallo standard ANSI C:

void free(void *ptr);

La funzione libera fa sì che lo spazio indicato da ptr venga deallocato, ovvero reso disponibile per un'ulteriore allocazione. Se ptr è un puntatore nullo, non si verifica alcuna azione. Altrimenti, se l'argomento non corrisponde a un puntatore precedentemente restituito dalla funzione calloc, malloc o realloc o se lo spazio è stato deallocato da una chiamata 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;
}

@DrPizza - Un errore (secondo me) è qualcosa che impedisce al tuo programma di funzionare come dovrebbe. Se un doppio libero nascosto rompe il tuo programma, è un errore. Se funziona esattamente come previsto, non è un errore.
Chris Lutz,

@DrPizza: ho appena trovato un argomento sul perché uno dovrebbe impostarlo NULLper evitare errori di mascheramento. stackoverflow.com/questions/1025589/… Sembra che in entrambi i casi alcuni errori vengano nascosti.
Georg Schölly,

1
Essere consapevoli del fatto che un vuoto da puntatore a puntatore ha i suoi problemi: c-faq.com/ptrs/genericpp.html
Sicuro il

3
@ Chris, no, l'approccio migliore è la struttura del codice. Non lanciare malloc casuali e liberare tutto il tuo codebase, mantieni insieme le cose correlate. Il "modulo" che alloca una risorsa (memoria, file, ...) è responsabile della sua liberazione e deve fornire una funzione per fare in modo che si prenda cura anche dei puntatori. Per qualsiasi risorsa specifica, hai quindi esattamente un posto dove viene allocato e un posto dove viene rilasciato, entrambi vicini.
Sicuro il

4
@Chris Lutz: Hogwash. Se si scrive codice che libera due volte lo stesso puntatore, il programma presenta un errore logico. Mascherare quell'errore logico facendolo non andare in crash non significa che il programma sia corretto: sta ancora facendo qualcosa di insensato. Non esiste uno scenario in cui è giustificata la scrittura di un doppio libero.
DrPizza,

4

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 il disordine inutile", quindi non lo faccio come 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 quindi il "contratto" se ti piace della classe è che quel membro punterà a qualcosa di valido in qualsiasi momento, quindi deve essere impostato su null per tale 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 in questo caso è 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 ora.

Un modello comune consiste nell'impostare tutti i puntatori membri su NULL nel costruttore e fare in modo che la chiamata del distruttore venga eliminata su qualsiasi puntatore 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 è parte di un progetto più ampio e 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.


4

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

È la migliore pratica 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;

3

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 prolungarsi, 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 lezioni ben progettate.


2

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 di simile a:


if(ptr)
   ptr->CallSomeMethod();

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


5
In molti casi, dove un puntatore NULL non ha senso, sarebbe preferibile scrivere un'asserzione.
Erich Kitzmueller,

2

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();
}

pfinisce nello stesso posto nello stack del primo nPtr, quindi potrebbe ancora contenere un puntatore apparentemente valido. Assegnare a *ppotrebbe 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 in modo casuale ...


2

Impostare il puntatore che è stato appena liberato su NULL non è obbligatorio ma è una buona pratica. In questo modo, è possibile evitare 1) usando un puntatore liberato 2) liberarlo gratuitamente


2

Impostazioni 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 accumula corruzione o arresta 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 () è quello di impostare quel puntatore su NULL in modo che anche se free () viene richiamato, ora viene chiamato per un puntatore NULL e un comportamento indefinito viene eluso.


2

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 fa attenzione. 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" di AIX.


2

Alla domanda originale: impostare il 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 offerta una serie 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 di 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 vero 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 eliminarli chiaramente, sembrerebbero 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


Buona risposta. Vorrei aggiungere una cosa. La revisione del database dei bug è utile per vari motivi. Ma nel contesto della domanda originale, tieni presente che sarebbe difficile sapere quanti problemi di puntatore non validi sono stati prevenuti, o almeno individuati così presto da non averli inseriti nel database dei bug. La cronologia dei bug fornisce prove migliori per l'aggiunta di regole di codifica.
Jimhark,

2

Ci sono due ragioni:

Evita gli arresti anomali durante il doppio rilascio

Scritto da RageZ in una domanda duplicata .

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 è quella di impostare NULL, in modo da poter fare test e verificare se è davvero necessario liberare questa memoria

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

anche da notare che free(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 allo sviluppatore la gestione della memoria nel suo insieme.

Evitare l'uso di puntatori già liberati

Scritto da Martin v. Löwis in un'altra risposta .

L'impostazione di puntatori inutilizzati su NULL è uno stile difensivo, che protegge da bug puntatori sospesi. 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.


1

Poiché disponi di un team di controllo della qualità, lasciami aggiungere un piccolo punto 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 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

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


1

È sempre consigliabile dichiarare una variabile puntatore con NULL come,

int *ptr = NULL;

Diciamo che ptr punta a un indirizzo di memoria 0x1000 . Dopo l'uso free(ptr), è sempre consigliabile annullare la variabile puntatore dichiarando nuovamente 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 puntatore pendente . Se si definisce un'altra variabile di puntatore (diciamo, q ) e si assegna dinamicamente l'indirizzo al nuovo puntatore, esiste la possibilità di prendere lo stesso indirizzo ( 0x1000 ) con una nuova variabile di puntatore. Se nel caso, si utilizza lo stesso puntatore ( ptr ) e si aggiorna il valore all'indirizzo indicato dallo stesso puntatore ( ptr ), il programma finirà per scrivere un valore nel punto in cui punta q (poiché p e q sono puntando allo stesso indirizzo (0x1000 )).

per esempio

*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.

1

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 tale 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.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.