2009-12-16 10 views
9

So che quando delete [] causerà la distruzione di tutti gli elementi dell'array e quindi rilascia la memoria.Perché [] viene utilizzato in delete (delete []) per liberare array allocati dinamicamente?

inizialmente ho pensato che compilatore vuole solo chiamare distruttore per tutti gli elementi della matrice, ma ho anche un contatore - argomento per ciò che è:

mucchio memoria allocatore deve conoscere la dimensione di byte allocati e usando sizeof(Type) è possibile trovare no di elementi e chiamare il numero appropriato di distruttori per un array per prevenire perdite di risorse.

Quindi la mia ipotesi è corretta o meno e per favore chiarisci il mio dubbio su di esso.

Quindi non ricevo l'utilizzo di [] in delete []?

risposta

34

Scott Meyers dice nel suo libro Effective C++: Item 5: Utilizzare lo stesso modulo negli usi corrispondenti di new e delete.

La grande domanda per eliminare è questa: quanti oggetti risiedono nella memoria che si sta cancellando? La risposta a ciò determina quanti distruttori devono essere chiamati.

Il puntatore che viene eliminato punta a un singolo oggetto oa una matrice di oggetti? L'unico modo per cancellare sapere è che tu lo dica. Se non si utilizzano parentesi nell'uso di Elimina, Elimina assume che un singolo oggetto sia puntato.

Inoltre, l'allocatore memoria potrebbe allocare più spazio necessario per memorizzare gli oggetti e in questo caso dividendo la dimensione del blocco di memoria restituita dalla dimensione di ciascun oggetto non funzionerà.

A seconda della piattaforma, i _msize (finestre), malloc_usable_size (Linux) o malloc_size funzioni (OSX) vi dirà la vera lunghezza del blocco che è stato assegnato. Queste informazioni possono essere sfruttate nella progettazione di contenitori in crescita.

Un altro motivo per cui non funziona è che Foo* foo = new Foo[10] chiama operator new[] per allocare la memoria. Quindi delete [] foo; chiama operator delete[] per deallocare la memoria. Poiché tali operatori possono essere sovraccaricati, è necessario attenersi alla convenzione altrimenti operator delete chiamate che possono avere un'implementazione incompatibile con operator delete []. È una questione di semantica, non solo di tenere traccia del numero di oggetti allocati per poi rilasciare il giusto numero di chiamate distruttore.

Consulta anche:

[16.14] After p = new Fred[n], how does the compiler know there are n objects to be destructed during delete[] p?

Risposta breve: Magia.

Risposta lunga: il sistema di runtime memorizza il numero di oggetti, n, da qualche parte dove può essere recuperato se si conosce solo il puntatore, p. Ci sono due tecniche popolari che fanno questo. Entrambe queste tecniche sono utilizzate da compilatori di livello commerciale, entrambi hanno dei compromessi, e nessuno dei due è perfetto.Queste tecniche sono:


EDIT: dopo aver commenti @AndreyT leggere, ho scavato nella mia copia di Stroustrup di "The Design e l'evoluzione di C++" e Tratto il seguente:

Come possiamo garantire che un array è stato correttamente cancellato? In particolare, come possiamo garantire che il distruttore venga chiamato per tutti gli elementi di un array?

...

Plain cancellare non è tenuto a gestire sia singoli oggetti un array. Ciò evita di complicare il caso comune di allocazione e deallocazione di singoli oggetti. Evita inoltre di ingombrare oggetti singoli con le informazioni necessarie per la deallocazione dell'array.

Una versione intermedia di delete [] richiedeva al programmatore di specificare il numero di elementi dell'array.

...

Tale rivelata troppo soggetto a errori, così l'onere di mantenere traccia del numero di elementi è stato posto sull'attuazione invece.

Come detto da @Marcus, il razionale potrebbe essere stato "non si paga per quello che non si usa".


EDIT2:

In "The C++ Programming Language, 3 ° edizione", §10.4.7, Bjarne Stroustrup scrive:

Esattamente come gli array e singoli oggetti sono allocati è specifica di esecuzione dipendente. Pertanto, diverse implementazioni reagiranno in modo diverso agli usi non corretti degli operatori delete ed delete []. In casi semplici e non interessanti come il precedente, un compilatore può rilevare il problema, ma in genere qualcosa di brutto si verifica in fase di esecuzione.

L'operatore di distruzione speciale per gli array, delete [], non è logicamente necessario. Tuttavia, supponiamo che l'implementazione dell'archivio gratuito sia stata richiesta per contenere informazioni sufficienti per ogni oggetto per stabilire se si trattava di un individuo o di un array. L'utente avrebbe potuto essere sollevato da un onere, ma quell'obbligo avrebbe imposto significativi overhead di tempo e spazio su alcune implementazioni C++.

+0

+1 per il libro di Scott Meyers: tutti i programmatori C++ dovrebbero possedere e leggere i libri C++ efficaci e più efficaci C++. – AAT

+2

La * spiegazione * di Scott non è affatto una spiegazione. È una mera asserzione del fatto, astutamente spacciata come una "spiegazione". Dice che l'unico modo per sapere se si tratta di un array o di un singolo oggetto è chiedere all'utente. Questo è, naturalmente, errato. È perfettamente possibile memorizzare tali informazioni in 'new []' e quindi recuperare in 'delete', proprio come è ora con il conteggio degli elementi. La decisione è stata presa contro di essa, perché avrebbe eccessivamente complicato la struttura delle informazioni "domestiche" e le ramificazioni in "delete". Non c'è una spiegazione "bella", è stata semplicemente * deciso * in quel modo. – AnT

+1

@Jason: No, la spiegazione di Scott è completamente falsa. Di nuovo, come fai a sapere quanti elementi ci sono nell'array quando fai 'delete []'? Eh? Devi dirgli di 'cancellare [] 'te stesso? No. 'cancella []' "sa" perché utilizza le informazioni sulla famiglia preparate da 'new []'. In exacty allo stesso modo potremmo forzare 'new' \' new [] 'per memorizzare ulteriori informazioni sulla famiglia di" array o non "natura. È facile e ovvio. Questa è la risposta alla tua domanda. – AnT

3

L'heap conosce la dimensione del blocco assegnato: è necessario solo l'indirizzo. Sembra che free() funzioni: si passa solo l'indirizzo e si libera memoria.

La differenza tra delete (delete[]) e free() è che i primi due prima chiamata distruttori, quindi memoria libera (possibilmente utilizzando free()). Il problema è che anche delete[] ha un solo argomento: l'indirizzo e il solo indirizzo devono conoscere il numero di oggetti su cui eseguire i distruttori.Pertanto, new[] utilizza un metodo di scrittura definito dall'implementazione in qualche parte del numero di elementi, di solito antepone l'array al numero di elementi. Ora delete [] farà affidamento su quei dati specifici dell'implementazione per eseguire i distruttori e quindi libererà memoria (di nuovo, usando solo l'indirizzo del blocco).

+0

Il compilatore non dovrebbe conoscere anche la dimensione degli oggetti assegnati (compresa l'eventuale imbottitura). In questo modo, data la dimensione totale del blocco di memoria allocato, il runtime dovrebbe essere in grado di determinare se sta deallocando uno o più oggetti in un array. – Giorgio

+0

@Giorgio: Nelle implementazioni tipiche 'new []' antepone l'array al numero di elementi e scosta il puntatore in modo che punti verso lo 0 ° elemento dell'array, non l'inizio del blocco. Quando chiami "cancella" tutto ciò che hai è un indirizzo che è compensato e non hai mezzi universali per trovarlo. – sharptooth

-1

Questo è più complicato.

La parola chiave e la convenzione per utilizzarlo per eliminare un array è stata inventata per la comodità delle implementazioni e alcune implementazioni lo usano (non so quale sia il caso, MS VC++ no).

La convenienza è questo:

In tutti gli altri casi, si sa l'esatta dimensione di essere liberato con altri mezzi. Quando elimini un singolo oggetto, puoi avere la dimensione da sizeof() in fase di compilazione. Quando si elimina un oggetto polimorfico per puntatore di base e si dispone di un distruttore virtuale, è possibile avere la dimensione come voce separata in vtbl. Se elimini una matrice, come faresti a sapere la dimensione della memoria da liberare, a meno che non la segui separatamente?

La sintassi speciale consentirebbe di tracciare tali dimensioni solo per un array, ad esempio, inserendolo prima dell'indirizzo restituito all'utente. Ciò richiede risorse aggiuntive e non è necessario per i non array.

+0

Riguarda il rilevamento del numero di elementi che richiedono il funzionamento dei propri distruttori, non la quantità di memoria allocata. –

1

delete[] chiama solo un'implementazione diversa (funzione);

Non c'è alcun motivo per cui un allocatore non può registrare (in effetti, sarebbe abbastanza semplice scrivere il proprio).

Non so il motivo per cui non l'hanno gestito, o la storia della realizzazione, se dovessi indovinare: molti di questi "bene, perché non era leggermente più semplice?" domande (in C++) è venuto giù per uno o più di:

  1. compatibilità con C
  2. prestazioni

In questo caso, le prestazioni. Usare delete vs delete[] è abbastanza facile, credo che possa essere tutto astratto dal programmatore ed essere ragionevolmente veloce (per uso generale). delete[] richiede solo poche chiamate e operazioni aggiuntive (omettendo le chiamate distruttore), ma è per chiamata per eliminare e non necessario perché il programmatore generalmente conosce il tipo con cui ha a che fare (in caso contrario, è probabile che sia più grande problema a portata di mano). Quindi evita semplicemente di chiamare l'allocatore. Inoltre, queste allocazioni singole potrebbero non aver bisogno di essere tracciate dall'allocatore in tutti i dettagli; Trattare ogni allocazione come una matrice richiederebbe voci aggiuntive per il conteggio per allocazioni banali, quindi è più livelli di semplici semplificazioni dell'implementazione degli allocatori che sono effettivamente importanti per molte persone, considerando che si tratta di un dominio di livello molto basso.

9

Il motivo principale per cui è stato deciso di mantenere separato delete e delete[] è che queste due entità non sono così simili come potrebbero sembrare a prima vista. Per un osservatore ingenuo potrebbero sembrare quasi gli stessi: basta distruggersi e deallocarsi, con l'unica differenza nel numero potenziale di oggetti da elaborare. In realtà, la differenza è molto più significativa.

La differenza più importante tra i due è che delete compia polimorfica eliminazione di oggetti, vale a dire il tipo statico dell'oggetto in questione potrebbe essere diverso dal suo tipo dinamico.delete[] d'altra parte deve occuparsi della cancellazione rigorosamente non polimorfica degli array. Quindi, internamente queste due entità implementano una logica che è significativamente diversa e non intersecante tra i due. A causa della possibilità di cancellazione polimorfica, la funzionalità di delete non è nemmeno lontanamente uguale alla funzionalità di delete[] su un array di 1 elemento, poiché un osservatore ingenuo potrebbe assumere in modo errato inizialmente.

Contrariamente alle strane affermazioni fatte in alcune altre risposte, è, naturalmente, perfettamente possibile sostituire delete e delete[] con un solo costrutto che si diramerebbe nella fase iniziale, cioè determinerebbe il tipo di blocco di memoria (array o non) utilizzando le informazioni sulla famiglia che verrebbero memorizzate da new/new[] e quindi passare alla funzionalità appropriata, equivalente a delete o delete[]. Tuttavia, questa sarebbe una decisione progettuale piuttosto scadente, poiché, ancora una volta, la funzionalità dei due è troppo diversa. Costringere entrambi in un singolo costrutto sarebbe come creare un coltellino svizzero con funzione di deallocazione. Inoltre, per essere in grado di distinguere un array da un non-array, dovremmo introdurre un'ulteriore informazione domestica anche in allocazioni di memoria a singolo oggetto fatte con lo standard new. Ciò potrebbe facilmente comportare un notevole sovraccarico della memoria nelle assegnazioni di singoli oggetti.

Ma, ancora una volta, il motivo principale qui è la differenza funzionale tra delete e delete[]. Queste entità linguistiche possiedono solo un'apparente somiglianza profonda della pelle che esiste solo al livello delle specifiche naive ("distruzione e memoria libera"), ma una volta che si capisce in dettaglio che cosa veramente queste entità devono fare, ci rendiamo conto che sono troppo diverse per essere uniti in uno solo.

P.S. Questo è BTW uno dei problemi con il suggerimento su sizeof(type) che hai fatto nella domanda. A causa della natura potenzialmente polimorfica di delete, non si conosce il tipo in delete, motivo per cui non è possibile ottenere alcun sizeof(type). Ci sono più problemi con questa idea, ma quella è già sufficiente per spiegare perché non volerà.