2011-12-06 10 views
18

Ho letto che l'operatore delete[] è necessario perché l'ambiente di runtime non conserva informazioni su se il blocco assegnato è un array di oggetti che richiedono o meno chiamate di distruttore, ma mantiene effettivamente le informazioni su dove è allocata la memoria blocco memorizzato, e anche, ovviamente, la dimensione del blocco.
Ci vorrebbe solo un altro bit di metadati per ricordare se i distruttori devono essere chiamati su eliminazione o no, quindi perché non farlo?Perché l'ambiente runtime non può decidere di applicare delete o delete [] al posto del programmatore?

Sono abbastanza sicuro che ci sia una buona spiegazione, non mi sto interrogando, vorrei solo saperlo.

+0

Stai chiedendo perché c'è 'delete []' invece di 'delete' che gestisce la deallocazione e la distruzione dell'array? –

+0

È un piccolo costo, ma poi il guadagno per pagarlo è anche piccolo. –

+2

A meno che lo standard non indichi esplicitamente che la dimensione dell'allocazione è memorizzata da qualche parte (ne dubito), non è sempre necessario memorizzare la dimensione allocata. Prendiamo il seguente esempio: 'int * p = new int;' L'unico modo per eliminarlo in un modo definito è chiamare delete su un 'int *'. Quindi la dimensione è implicitamente nota dal fatto che si sa che è un 'int'. (Nota, chiamando delete su un 'void *' è UB: http://stackoverflow.com/a/941959/239916). –

risposta

3

Ci sono due cose che devono essere chiarite qui.

Primo: l'assunto che malloc mantenga la dimensione esatta richiesta.

Non proprio. malloc si preoccupa solo di fornire un blocco sufficientemente grande. Sebbene, per ragioni di efficienza, probabilmente non sarà molto utile in generale, probabilmente ti darà un blocco di dimensioni "standard", ad esempio un blocco di byte 2^n. Pertanto la dimensione reale (come in, il numero di oggetti effettivamente allocati) è effettivamente sconosciuta.

Secondo: il "qualcosa in più" necessaria

Infatti, le informazioni necessarie per un determinato oggetto per sapere se è parte di un array o non sarebbe semplicemente un po 'in più. Logicamente.

Per quanto riguarda l'implementazione, però: dove metteresti effettivamente quel pezzo?

La memoria allocata per l'oggetto stesso probabilmente non deve essere toccata, dopotutto l'oggetto lo sta utilizzando. Così ?

  • su qualche piattaforma, questo potrebbe essere tenuto nel puntatore stesso (alcune piattaforme ignorano una porzione dei bit), ma questo non è portabile
  • quindi richiederebbe memoria supplementare, almeno un byte, tranne che con problemi di allineamento potrebbe ammontare a 8 byte.

Dimostrazione: (non convincente come notato da qc, vedi sotto)

// A plain array of doubles: 
+-------+-------+------- 
| 0 | 1 | 2 
+-------+-------+------- 

// A tentative to stash our extra bit 
+-------++-------++-------++ 
| 0 || 1 || 2 || 
+-------++-------++-------++ 

// A correction since we introduced alignment issues 
// Note: double's aligment is most probably its own size 
+-------+-------+-------+-------+-------+------- 
| 0 | bit | 1 | bit | 2 | bit 
+-------+-------+-------+-------+-------+------- 

Humpf!

EDIT

Pertanto, sulla maggior parte delle piattaforme (in cui l'indirizzo non importa), è necessario "estendere" ogni puntatore, e in realtà raddoppiare le loro dimensioni (problemi di allineamento).

È accettabile che tutti i puntatori siano due volte più grandi solo in modo da poter riporre quel bit in più? Per la maggior parte delle persone credo che sarebbe. Ma C++ non è progettato per la maggior parte delle persone, è progettato principalmente per le persone che hanno a cuore le prestazioni, che siano velocità o memoria, e come tale non è accettabile.

FINE EDIT

Allora, qual è la risposta corretta? La risposta corretta è che il recupero delle informazioni che il sistema di tipi ha perso è costoso. Purtroppo.

+1

Non è necessario archiviare un bit per ogni elemento dell'array poiché non è valido per eliminare un puntatore a un elemento "all'interno" dell'array. Solo per il primo elemento devi essere in grado di determinare se un intero array non lo sta seguendo. – sth

+0

@sth: vero, la dimostrazione è difettosa ... e ho dato tanto amore al mio disegno ASCII :(Ho corretto, grazie per l'avviso –

+0

Ancora, hai controllato il sovraccarico degli allocatori di heap gneral? –

10

Penso che il motivo sia che il C++ non ti obbliga a tutto ciò che non vuoi. Aggiungerebbe ulteriori metadati e se qualcuno non la usasse, tale overhead aggiuntivo sarebbe forzato su di loro, in contrasto con gli obiettivi di design del linguaggio C++.

Quando si desidera la funzionalità descritta, C++ corrisponde a. Si chiama std::vector e dovresti quasi sempre preferirlo, un altro tipo di contenitore o un puntatore intelligente su raw new e delete.

+0

Probabilmente ... anche se si potrebbe obiettare che la semantica dell'array in C fosse già rotta, quindi da lì sta andando avanti. Ho paura che lasciare fuori le dimensioni fosse una prematura micro-ottimizzazione. –

+0

Non penso che questa fosse la domanda. –

+0

Informazioni su 'std :: vector' ... non proprio perché può crescere dinamicamente. Un contenitore più semplice funzionerebbe. Tuttavia, abbastanza vicino. –

4

C++ ti consente di essere efficiente il più possibile, quindi se dovessero tenere traccia del numero di elementi in un blocco sarebbero solo 4 byte aggiuntivi utilizzati per blocco.

Questo potrebbe essere utile a molte persone, ma impedisce anche l'efficienza totale per le persone a cui non importa mettere [].

È simile alla differenza tra C++ e Java. Java può essere molto più veloce da programmare perché non devi mai preoccuparti della garbage collection, ma il C++, se programmato correttamente, può essere più efficiente e utilizzare meno memoria perché non deve memorizzare nessuna di queste variabili e puoi decidere quando cancella i blocchi di memoria.

+2

Ora vai a controllare qualsiasi allocatore di heap di uso generale e vedere quanto overhead ha per ogni allocazione. Non sarei sorpreso da numeri come 16-64 byte per ogni allocazione, quindi da 4 a 8 byte per dimensione nessuno noterà. E ancora, probabilmente la maggior parte degli allocatori tiene comunque traccia delle allocazioni. –

4

Fondamentalmente si riduce al linguaggio design che non vuole mettere troppe restrizioni sugli implementatori. Molti runtime in C++ utilizzano malloc() per ::operator new() e free() (più o meno) per ::operator delete(). Lo standard malloc/free non fornisce la contabilità necessaria per la registrazione di un numero di elementi e non fornisce alcun modo per determinare la dimensione malloc al tempo free. Aggiungere un altro livello di manipolazione della memoria tra new Foo e malloc per ogni singolo oggetto è, dal punto di vista C/C++, un salto abbastanza grande nella complessità/astrazione. Tra l'altro, l'aggiunta di questo overhead a tutti gli oggetti potrebbe rovinare alcuni approcci di gestione della memoria che sono stati progettati sapendo quale sia la dimensione degli oggetti.