Domanda

Vedo spesso il controllo del codice legacy per NULL prima di eliminare un puntatore, simile a

if (NULL != pSomeObject) 
{
    delete pSomeObject;
    pSomeObject = NULL;
}

C'è qualche motivo per cercare un puntatore NULL prima di eliminarlo? Qual è il motivo per impostare il puntatore su NULL in seguito?

È stato utile?

Soluzione

È perfettamente " sicuro " per eliminare un puntatore nullo; equivale effettivamente a una no-op.

Il motivo per cui potresti voler controllare null prima di eliminare è che il tentativo di eliminare un puntatore null potrebbe indicare un bug nel tuo programma.

Altri suggerimenti

Lo standard C ++ garantisce che è legale usare un puntatore null in una cancellazione-espressione (§8.5.2.5 / 2). Tuttavia, è non specificato se questo chiamerà una funzione di deallocazione ( operator delete o operator delete [] ; §8.5.2.5 / 7, nota ).

Se una funzione di deallocazione predefinita (ovvero fornita dalla libreria standard) viene chiamata con un puntatore nullo, la chiamata non ha alcun effetto (§6.6.4.4.2 / 3).

Ma non è specificato cosa succede se la funzione di deallocazione non è fornita dalla libreria standard - & nbsp; cioè. cosa succede quando sovraccarichiamo operatore delete (o operator delete [] ).

Un programmatore competente gestirà i puntatori null di conseguenza dentro la funzione di deallocazione, piuttosto che prima della chiamata, come mostrato nel codice OP. Allo stesso modo, impostando il puntatore su nullptr / NULL dopo l'eliminazione ha uno scopo molto limitato. Ad alcune persone piace farlo nello spirito di programmazione difensiva : migliorerà leggermente il comportamento del programma prevedibile nel caso di un bug: l'accesso al puntatore dopo l'eliminazione comporterà un accesso al puntatore nullo anziché un accesso a una posizione di memoria casuale. Sebbene entrambe le operazioni siano un comportamento indefinito, il comportamento di un accesso con puntatore null è molto più prevedibile nella pratica (molto spesso provoca un arresto diretto anziché un danneggiamento della memoria). Poiché i danneggiamenti della memoria sono particolarmente difficili da eseguire il debug, il ripristino dei puntatori eliminati aiuta il debug.

- Ovviamente si tratta del sintomo piuttosto che della causa (cioè del bug). Dovresti considerare il ripristino dei puntatori come un odore di codice. Il codice C ++ moderno e pulito renderà la proprietà della memoria chiara e controllata staticamente (usando puntatori intelligenti o meccanismi equivalenti), e quindi in modo dimostrabile evitare questa situazione.

Bonus

: spiegazione dell'eliminazione dell'operatore sovraccarico :

operator delete è (nonostante il suo nome) una funzione che può essere sovraccaricata come qualsiasi altra funzione. Questa funzione viene chiamata internamente per ogni chiamata di operator delete con argomenti corrispondenti. Lo stesso vale per operatore nuovo .

Sovraccarico dell'operatore nuovo (e quindi anche operatore cancella ) ha senso in alcune situazioni in cui si desidera controllare con precisione come viene allocata la memoria. Fare questo non è nemmeno molto difficile, ma è necessario prendere alcune precauzioni per garantire un comportamento corretto. Scott Meyers lo descrive dettagliatamente C ++ efficace .

Per ora, diciamo solo che vogliamo sovraccaricare la versione globale di operatore nuovo per il debug. Prima di farlo, un breve preavviso su ciò che accade nel seguente codice:

klass* pobj = new klass;
// … use pobj.
delete pobj;

Cosa succede realmente qui? Bene, quanto sopra può essere approssimativamente tradotto nel seguente codice:

// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();

// … use pobj.

// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);

Nota il passaggio 2 in cui chiamiamo new con una sintassi leggermente strana. Questa è una chiamata al cosiddetto posizionamento nuovo che prende un indirizzo e costruisce un oggetto a quell'indirizzo. Anche questo operatore può essere sovraccarico. In questo caso, serve solo a chiamare il costruttore della classe klass .

Ora, senza ulteriori indugi, ecco il codice per una versione sovraccarica degli operatori:

void* operator new(size_t size) {
    // See Effective C++, Item 8 for an explanation.
    if (size == 0)
        size = 1;

    cerr << "Allocating " << size << " bytes of memory:";

    while (true) {
        void* ret = custom_malloc(size);

        if (ret != 0) {
            cerr << " @ " << ret << endl;
            return ret;
        }

        // Retrieve and call new handler, if available.
        new_handler handler = set_new_handler(0);
        set_new_handler(handler);

        if (handler == 0)
            throw bad_alloc();
        else
            (*handler)();
    }
}

void operator delete(void* p) {
    cerr << "Freeing pointer @ " << p << "." << endl;
    custom_free(p);
}

Questo codice utilizza solo un'implementazione personalizzata di malloc / free internamente, come la maggior parte delle implementazioni. Crea anche un output di debug. Considera il seguente codice:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

Ha prodotto il seguente output:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Ora, questo codice fa qualcosa di sostanzialmente diverso dall'implementazione standard di operator delete : Non ha testato puntatori nulli! Il compilatore non lo controlla così il codice sopra si compila ma può dare errori cattivi in ??fase di esecuzione quando si tenta di eliminare i puntatori null.

Tuttavia, come ho detto prima, questo comportamento è in realtà inaspettato e uno scrittore di librerie dovrebbe fare attenzione a verificare la presenza di puntatori nulli nell'operatore elimina . Questa versione è molto migliorata:

void operator delete(void* p) {
    if (p == 0) return;
    cerr << "Freeing pointer @ " << p << "." << endl;
    free(p);
}

In conclusione, sebbene un'implementazione sciatta di operator delete possa richiedere controlli null espliciti nel codice client, si tratta di un comportamento non standard e dovrebbe essere tollerato solo nel supporto legacy ( se at tutti ).

Elimina i controlli per NULL internamente. Il test è ridondante

L'eliminazione di null è una no-op. Non c'è motivo di verificare la presenza di null prima di chiamare delete.

Potresti voler controllare null per altri motivi se il puntatore essendo null contiene alcune informazioni aggiuntive che ti interessano.

Secondo C ++ 03 5.3.5 / 2, è sicuro eliminare un puntatore null. Ciò che segue è citato dallo standard:

  

In entrambe le alternative, se il valore dell'operando di delete è il   puntatore null l'operazione non ha alcun effetto.

Se pSomeObject è NULL, delete non farà nulla. Quindi no, non è necessario controllare NULL.

Riteniamo buona prassi assegnare NULL al puntatore dopo averlo eliminato se è possibile che qualche testa di cavallo possa tentare di usare il puntatore. L'uso di un puntatore NULL è leggermente migliore rispetto all'utilizzo di un puntatore a chissà cosa (il puntatore NULL provocherà un arresto anomalo, il puntatore alla memoria eliminata potrebbe non esserlo)

Non c'è motivo di verificare la presenza di NULL prima dell'eliminazione. L'assegnazione di NULL dopo l'eliminazione potrebbe essere necessaria se da qualche parte nei controlli del codice viene effettuato se un oggetto è già allocato eseguendo un controllo NULL. Un esempio potrebbe essere una sorta di dati memorizzati nella cache allocati su richiesta. Ogni volta che si cancella l'oggetto cache si assegna NULL al puntatore in modo che il codice che alloca l'oggetto sappia che deve eseguire un'allocazione.

Credo che lo sviluppatore precedente lo abbia codificato " ridondante " per risparmiare alcuni millisecondi: È una buona cosa avere il puntatore impostato su NULL dopo essere stato eliminato, quindi puoi usare una linea come la seguente subito dopo aver eliminato l'oggetto:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

Ma poi eliminare sta facendo lo stesso confronto esatto (non fa nulla se è NULL). Perché farlo due volte? Puoi sempre assegnare pSomeObject a NULL dopo aver chiamato delete, indipendentemente dal suo valore corrente, ma sarebbe leggermente ridondante se avesse già quel valore.

Quindi la mia scommessa è che l'autore di quelle righe ha cercato di assicurarsi che pSomeObject1 sarebbe sempre NULL dopo essere stato eliminato, senza incorrere nel costo di un test e di un'assegnazione potenzialmente inutili.

Dipende da cosa stai facendo. Alcune implementazioni precedenti di free , ad esempio, non saranno contente se gli viene passato un puntatore NULL . Alcune librerie hanno ancora questo problema. Ad esempio, XFree nella libreria Xlib dice:

  

DESCRIZIONE

     

La funzione XFree è a   routine Xlib per tutti gli usi   libera i dati specificati. Devi   usalo per liberare tutti gli oggetti che erano   allocato da Xlib, a meno che non sia un supplente   La funzione è esplicitamente specificata per   l'oggetto. Un puntatore NULL non può essere   passato a questa funzione.

Quindi considera di liberare i puntatori NULL come bug e sarai al sicuro.

Per quanto riguarda le mie osservazioni, l'eliminazione di un puntatore null usando delete è sicura nelle macchine basate su unix come PARISC e itanium. Ma non è abbastanza sicuro per i sistemi Linux in quanto il processo andrebbe in crash allora.

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