2009-12-16 7 views
23

Prima di tutto, usare delete per qualsiasi cosa allocata con new[] è un comportamento non definito secondo lo standard C++.In che modo l'abbinamento nuovo [] con eliminazione può portare solo alla perdita di memoria?

In Visual C++ 7 tale abbinamento può portare a una delle due conseguenze.

Se il tipo di nuova [] 'ed ha costruttore banale e distruttore VC++ usa semplicemente new invece di new[] e l'utilizzo di delete per quel blocco funziona bene - new solo chiama 'allocare memoria', delete chiama semplicemente 'memoria libera'.

Se il tipo new [] 'ed ha un costruttore o distruttore non banale il trucco sopra non può essere fatto - VC++ 7 deve invocare esattamente il giusto numero di distruttori. Quindi prepara l'array con un size_t memorizzando il numero di elementi. Ora l'indirizzo restituito da new[] punti sul primo elemento, non all'inizio del blocco. Quindi, se viene utilizzato delete, chiama solo il distruttore per il primo elemento e le chiamate "memoria libera" con l'indirizzo diverso da quello restituito da "allocare memoria" e questo porta a qualche indicazione di errore all'interno di HeapFree() che a mio avviso si riferisce a corruzione dell'heap.

Eppure ogni qui e là si possono leggere dichiarazioni false che utilizzando delete dopo new[] porta a una perdita di memoria. Sospetto che qualsiasi dimensione della corruzione dell'heap sia molto più importante di un fatto che il distruttore è chiamato solo per il primo elemento e che probabilmente i distruttori non chiamati non hanno liberato oggetti secondari allocati all'heap.

In che modo l'utilizzo di delete dopo il new[] può portare solo a una perdita di memoria in alcune implementazioni C++?

+6

Per tutti i rispondenti: la domanda è come può portare a ** solo ** una perdita di memoria, cioè come può ** non ** causare il danneggiamento dell'heap. – Thomas

+2

Abbastanza facilmente. Tutto dipende da come viene scritta la gestione della memoria. Dal momento che questo non è definito dallo standard, tutte le risposte sono solo speculazioni (ma sono sicuro che potrei scrivere una versione che non manderebbe in crash l'heap ma abbia perso memoria). Il sottosistema di gestione della memoria è il più veloce ed efficiente possibile. Lo standard ha fornito loro una serie di condizioni pre e post in base alle quali il sottosistema può essere ottimizzato. Rompi queste condizioni e hai un comportamento indefinito (probabilmente il danneggiamento dell'heap). Nel debug, la stabilità e non la velocità sono l'obiettivo del sottosistema di memoria. Quindi la perdita è più probabile. –

+0

http://stackoverflow.com/questions/1553382/pod-freeing-memory-is-delete-equal-to-delete/1553407#1553407 – sbi

risposta

25

Supponiamo che io sia un compilatore C++ e implemento la gestione della mia memoria in questo modo: antepone ogni blocco di memoria riservata alla dimensione della memoria, in byte. Qualcosa come questo;

| size | data ... | 
     ^
     pointer returned by new and new[] 

noti che, in termini di allocazione di memoria, non c'è differenza tra new e new[]: entrambi solo allocare un blocco di memoria di una certa dimensione.

Ora come sarà il delete[] a conoscere la dimensione della matrice, al fine di chiamare il numero giusto di distruttori? È sufficiente dividere lo size del blocco di memoria con sizeof(T), dove T è il tipo di elementi dell'array.

Supponiamo ora di implementare delete come una sola chiamata al distruttore, seguita dalla liberazione dei byte size, quindi i distruttori degli elementi successivi non verranno mai chiamati. Ciò si traduce in perdite di risorse allocate dagli elementi successivi. Tuttavia, poiché I do byte liberi size (non sizeof(T) byte), non si verifica alcun danneggiamento dell'heap.

+0

Pollice su. Come hai appena detto, l'OP assume che nuove e nuove [] vengano gestite diversamente, ma potrebbe non essere il caso. "nuovo" può essere solo "nuovo []" con un valore di ante_t anteposto con valore 1. –

+0

In realtà intendevo "dimensione" per indicare il numero di byte, non di elementi. Qualcosa che una funzione come 'malloc' potrebbe fare. Modificherò un po 'il mio post per renderlo esplicito. – Thomas

+3

Se è stata utilizzata la tecnica di gestione della memoria. MA allora hai un sovraccarico di x byte per contenere le dimensioni, un aumento del 100% per oggetti di piccole dimensioni. Sì, potremmo pagare quel costo se volessimo compensare i programmatori cattivi. Ma non voglio pagare quel prezzo solo per supportare "sharptooth" quindi preferirei che la gestione della memoria sia molto efficiente (anche per i piccoli tipi). Di conseguenza lo standard non richiede e la maggior parte delle implementazioni non antepone le dimensioni per nuove nella versione di rilascio. Anche se alcuni fanno nella versione di debug solo per aiutare nel debugging/profiling. –

4

Se il distruttore non banale che non è chiamato per tutti tranne il primo elemento nell'array dovrebbe liberare della memoria si ottiene una perdita di memoria poiché questi oggetti non vengono ripuliti correttamente.

3

Oltre al risultato di un comportamento non definito, la causa più semplice delle perdite si trova nell'implementazione che non chiama il distruttore per tutti tranne il primo oggetto nell'array. Ciò ovviamente causerà perdite se gli oggetti hanno risorse allocate.

Questa è la classe più semplice possibile che riuscivo a pensare con conseguente questo comportamento:

struct A { 
     char* ch; 
     A(): ch(new char){} 
     ~A(){ delete ch; } 
    }; 

A* as = new A[10]; // ten times the A::ch pointer is allocated 

delete as; // only one of the A::ch pointers is freed. 

PS: notare che i costruttori non riescono a ottenere chiamato in un sacco di altri errori di programmazione, anche: i distruttori della classe base non virtuali , falsa dipendenza da puntatori intelligenti, ...

+0

@Suma: il problema che ho cercato di mostrare qui è come solo il distruttore del il primo oggetto è chiamato, risultante in 9 blocchi trapelati contenenti 1 'char'. Hai ragione riguardo alla matrice di elementi 'A', ma non era questa la domanda. – xtofl

+0

@Suma: non c'è niente di male nel sottolineare che la spiegazione _ era un po 'nascosta. Grazie per essere critico, ci __testo! – xtofl

3

Ciò causerà una perdita in TUTTE le implementazioni di C++ in tutti i casi in cui il distruttore libera la memoria, perché il distruttore non viene mai chiamato.

In alcuni casi può causare errori molto peggiori.

+1

-1 per "in alcuni casi" Una risposta professionale dovrebbe indicare un riferimento. per esempio. Articolo 5 di C++ efficace di S.Meyers: "Che cosa succederebbe se avessi usato il [] ... Il risultato non è definito. ... non è definito nemmeno per i tipi built-in ... La regola, quindi, è semplice: se usi [] quando chiami nuovo, devi usare [] quando chiami Cancella. Se non usi [] quando chiami nuovo, non usare [] quando chiami cancella. " –

3

perdita di memoria potrebbe accadere se l'operatore new() viene sovrascritto ma nuovo [] non lo è. lo stesso vale per l'operatore delete/delete []

6

Sembra che la tua domanda sia davvero "perché la corruzione dell'heap non accade?". La risposta a questo è "perché il gestore dell'heap tiene traccia delle dimensioni dei blocchi allocate". Torniamo a C per un minuto: se vuoi assegnare un singolo int in C, devi fare int* p = malloc(sizeof(int)), se vuoi assegnare un array di dimensioni n puoi scrivere int* p = malloc(n*sizeof(int)) o int* p = calloc(n, sizeof(int)). Ma in ogni caso lo libererai per free(p), indipendentemente da come lo hai assegnato. Non si passa mai la dimensione a free(), free() solo "sa" quanto liberare, poiché la dimensione di un blocco malloc() - ed è salvata da qualche parte "davanti" al blocco. Tornando al C++, new/delete e new []/delete [] sono di solito implementati in termini di malloc (anche se non devono esserlo, non dovresti fare affidamento su questo). Questo è il motivo per cui la nuova combinazione []/delete non corrompe l'heap: l'eliminazione libera la giusta quantità di memoria, ma, come spiegato da tutti prima di me, è possibile ottenere perdite non chiamando il numero corretto di distruttori.

Detto questo, ragionare sul comportamento non definito in C++ è sempre un esercizio inutile. Perché è importante se la nuova combinazione []/delete funziona, "solo" perdite o causa il danneggiamento dell'heap? Non dovresti scrivere un codice del genere, punto! E, in pratica, eviterei la gestione manuale della memoria ogni volta che è possibile - l'aumento di STL & esiste per un motivo.

8

La fiaba del mixaggio di new[] e delete che presumibilmente causa una perdita di memoria è proprio questo: una favola. Non ha assolutamente alcun fondamento nella realtà. Non so da dove venisse, oramai ha acquisito una vita propria e sopravvive come un virus, propagandosi dal passaparola da un principiante all'altro.

La più probabile logica dietro questa dialogo "perdita di memoria" è che dal punto innocentemente ingenuo di vista della differenza tra delete e delete[] è che delete viene utilizzato per distruggere un solo oggetto, mentre delete[] distrugge un array di oggetti (" molti "oggetti"). Una conclusione ingenua che di solito deriva da questo è che il primo elemento dell'array verrà distrutto da delete, mentre il resto persisterà, creando così la presunta "perdita di memoria". Naturalmente, qualsiasi programmatore che abbia almeno una conoscenza di base delle tipiche implementazioni dell'heap comprenderebbe immediatamente che la conseguenza più probabile è la corruzione dell'heap, non una "perdita di memoria".

Un'altra spiegazione popolare per l'ingenua teoria della "perdita di memoria" è che, poiché viene chiamato il numero errato di distruttori, la memoria secondaria di proprietà degli oggetti nell'array non viene deallocata. Questo potrebbe essere vero, ma è ovviamente una spiegazione molto forzata, che ha poca rilevanza di fronte a un problema molto più serio con la corruzione dell'heap.

In breve, la miscelazione di diverse funzioni di allocazione è uno di quegli errori che portano a un comportamento non definito solido, imprevedibile e pratico . Qualsiasi tentativo di imporre dei limiti concreti alle manifestazioni di questo comportamento indefinito è solo una perdita di tempo e un segno sicuro della mancanza di comprensione di base.

Inutile aggiungere, new/delete e new[]/delete[] sono infatti due meccanismi indipendenti di gestione della memoria, che sono personalizzabili in modo indipendente. Una volta che vengono personalizzati (sostituendo le funzioni di gestione della memoria non elaborata) non c'è assolutamente modo di iniziare a prevedere cosa potrebbe accadere se si mischiano.

2

In ritardo per una risposta, ma ...

Se il meccanismo di eliminazione è semplicemente quello di chiamare il distruttore e posizionare il puntatore liberato, insieme con la dimensione implicita sizeof, su una pila libera, quindi chiamando cancella su un blocco di memoria allocato con nuovo [] per cui la memoria viene persa, ma non per corruzione. Le strutture malloc più sofisticate potrebbero corrompere o rilevare questo comportamento.

2

Perché la risposta non può essere che causa entrambi?

Ovviamente la memoria è trapelata se la corruzione dell'heap si verifica o meno.

O meglio, dal momento che posso ri-implementare nuovo e cancellare ..... non può non causare nulla. Tecnicamente posso causare nuovi e cancellare per eseguire nuovi [] ed eliminare [].

HENCE: comportamento non definito.

+0

Sì, può causare entrambi, ma IMO una volta che si verifica un danneggiamento dell'heap non si cura più di una perdita di memoria. – sharptooth

+0

Il punto è che non è definito. Non importa quale sia la risposta conosciuta per la maggior parte dei compilatori. Se per caso esiste un compilatore che implementa nuovi e nuovi [], cancella e cancella [] in modi esattamente identici, non ci sarà mai un danneggiamento dell'heap. Pertanto la risposta alla domanda è, se il compilatore implementa in modo tale da evitare un danneggiamento dell'heap, quindi potrebbe solo causare una perdita di memoria. Tranne se il compilatore implementa in modo tale da evitare entrambi. Quindi è inutile rispondere a una domanda del genere a meno che non ci si riferisca a un compilatore specifico. –

1

Stavo rispondendo a una domanda che è stata contrassegnata come duplicata, quindi la copierò qui solo nel caso in cui la metta in crisi. È stato detto molto prima di me il modo in cui funziona l'allocazione della memoria, spiegherò solo la causa degli effetti &.

Solo una piccola cosa giusta fuori google: http://en.cppreference.com/w/cpp/memory/new/operator_delete

In ogni caso, eliminare è una funzione per un singolo oggetto. Libera l'istanza dal puntatore e lascia;

delete [] è una funzione utilizzata per deallocare array. Ciò significa che non si limita a liberare il puntatore; Dichiara l'intero blocco di memoria di quell'array come spazzatura.

In pratica è tutto molto bello, ma mi dici che la tua applicazione funziona. Probabilmente ti starai chiedendo ... perché?

La soluzione è C++ non riuscite perdite di memoria. Se userai cancella senza parentesi, eliminerà solo la matrice come oggetto - un processo che potrebbe causare una perdita di memoria .

bella storia, perdita di memoria, perché dovrebbe importarmi?

La perdita di memoria si verifica quando la memoria allocata non viene eliminata. Quella memoria quindi richiede uno spazio disco non necessario, che ti farà perdere memoria utile praticamente senza motivo. Questa è una cattiva programmazione e probabilmente dovresti risolverla nei tuoi sistemi.