In che modo delete [] sa che è un array?


136

Bene, penso che siamo tutti d'accordo sul fatto che ciò che accade con il seguente codice non è definito, a seconda di ciò che viene passato,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

Il puntatore potrebbe essere ogni sorta di cose diverse, quindi eseguire un incondizionato delete[]su di esso non è definito. Tuttavia, supponiamo che stiamo effettivamente passando un puntatore a matrice,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

La mia domanda è, in questo caso in cui il puntatore è un array, chi lo sa? Voglio dire, dal punto di vista del linguaggio / compilatore, non ha idea se si tratti o meno di arrun puntatore a matrice rispetto a un puntatore a un singolo int. Diamine, non sa nemmeno se è arrstato creato dinamicamente. Tuttavia, se invece faccio quanto segue,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

Il sistema operativo è abbastanza intelligente da eliminare solo un int e non andare avanti con un qualche tipo di "uccisione di follia" eliminando il resto della memoria oltre quel punto (contrasto che con strlene una \0stringa non terminata - continuerà fino a quando non risultati 0).

Quindi di chi è il compito di ricordare queste cose? Il sistema operativo mantiene un tipo di record in background? (Voglio dire, mi rendo conto di aver iniziato questo post dicendo che ciò che accade è indefinito, ma il fatto è che lo scenario di "omicidio di follia" non accade, quindi quindi nel mondo pratico qualcuno sta ricordando.)



6
lo sa dalle parentesi quadre dopo l'eliminazione
JoelFan,

"il puntatore è un array". No, i puntatori non sono mai array. Indicano spesso il primo elemento dell'array, ma questa è una cosa diversa.
Aaron McDaid il

Risposte:


99

Il compilatore non sa che è un array, si fida del programmatore. L'eliminazione di un puntatore a un singolo intcon delete []comporterebbe un comportamento indefinito. Il tuo secondo main()esempio non è sicuro, anche se non si blocca immediatamente.

Il compilatore deve tenere traccia di quanti oggetti devono essere eliminati in qualche modo. Può farlo eseguendo un'allocazione eccessiva sufficiente per memorizzare la dimensione dell'array. Per ulteriori dettagli, consultare le Domande frequenti su C ++ .


14
In realtà, usare delete [] per cancellare qualcosa creato con new è sfruttabile. taossa.com/index.php/2007/01/03/…
Rodrigo

23
@Rodrigo Il link nel tuo commento è interrotto, ma per fortuna la macchina del ritorno ne ha una copia su replay.web.archive.org/20080703153358/http://taossa.com/…
David Gardner

103

Una domanda a cui le risposte fornite finora non sembrano rispondere: se le librerie di runtime (non il sistema operativo, in realtà) possono tenere traccia del numero di cose nell'array, allora perché abbiamo bisogno della delete[]sintassi? Perché non è possibile utilizzare un singolo deletemodulo per gestire tutte le eliminazioni?

La risposta a ciò risale alle radici di C ++ come linguaggio C-compatibile (che non si sforza più di essere realmente). La filosofia di Stroustrup era che il programmatore non avrebbe dovuto pagare per nessuna delle funzionalità che non utilizzava. Se non utilizzano array, non dovrebbero dover sostenere il costo degli array di oggetti per ogni blocco di memoria allocato.

Cioè, se il tuo codice lo fa semplicemente

Foo* foo = new Foo;

quindi lo spazio di memoria allocato per foonon dovrebbe includere alcun sovraccarico aggiuntivo che sarebbe necessario per supportare array di Foo.

Poiché sono impostate solo allocazioni di array per trasportare le informazioni aggiuntive sulla dimensione dell'array, è necessario indicare alle librerie di runtime di cercare tali informazioni quando si eliminano gli oggetti. Ecco perché dobbiamo usare

delete[] bar;

invece di solo

delete bar;

se la barra è un puntatore a un array.

Per la maggior parte di noi (me compreso), quel pignolo su qualche byte in più di memoria sembra strano in questi giorni. Ma ci sono ancora alcune situazioni in cui il salvataggio di pochi byte (da quello che potrebbe essere un numero molto elevato di blocchi di memoria) può essere importante.


20
"la confusione di qualche byte in più di memoria sembra strana in questi giorni". Fortunatamente, a queste persone anche gli array nudi stanno iniziando a sembrare caratteristici, quindi possono semplicemente usare un vettore o boost :: array e dimenticare di eliminare [] per sempre :-)
Steve Jessop

28

Sì, il sistema operativo mantiene alcune cose in "background". Ad esempio, se corri

int* num = new int[5];

il sistema operativo può allocare 4 byte extra, memorizzare la dimensione dell'allocazione nei primi 4 byte della memoria allocata e restituire un puntatore offset (ovvero, alloca gli spazi di memoria da 1000 a 1024 ma il puntatore ha restituito punti a 1004, con posizioni 1000- 1003 memorizzazione della dimensione dell'allocazione). Quindi, quando viene chiamato delete, può guardare a 4 byte prima che il puntatore gli passi per trovare la dimensione dell'allocazione.

Sono sicuro che esistono altri modi per tenere traccia delle dimensioni di un'allocazione, ma questa è un'opzione.


26
+1 - punto valido in generale, tranne per il fatto che solitamente il runtime della lingua è responsabile della memorizzazione di questi metadati, non del sistema operativo.
sharptooth,

Cosa succede alla dimensione dell'array o alla dimensione di un oggetto che ha l'array definito? Mostra i 4 byte aggiuntivi quando si esegue una dimensione di quell'oggetto?
Shree,

3
No, sizeof mostra solo le dimensioni dell'array. Se il runtime sceglie di implementarlo con il metodo che ho descritto, questo è rigorosamente un dettaglio di implementazione e dal punto di vista dell'utente, dovrebbe essere mascherato. La memoria prima del puntatore non "appartiene" all'utente e non viene conteggiata.
bsdfish,

2
Ancora più importante, sizeof non restituirà la dimensione reale di un array allocato dinamicamente in ogni caso. Può restituire solo dimensioni note al momento della compilazione.
bdonlan,

È possibile utilizzare questi metadati in un ciclo for per eseguire il loop accurato sull'array? ad es. for(int i = 0; i < *(arrayPointer - 1); i++){ }
Sam

13

Questo è molto simile a questa domanda e ha molti dei dettagli che stai cercando.

Ma basti dire che non è compito del sistema operativo tenere traccia di tutto ciò. In realtà sono le librerie di runtime o il gestore della memoria sottostante a tenere traccia delle dimensioni dell'array. Questo di solito viene fatto allocando memoria aggiuntiva in primo piano e memorizzando le dimensioni dell'array in quella posizione (la maggior parte utilizza un nodo head).

Questo è visualizzabile su alcune implementazioni eseguendo il seguente codice

int* pArray = new int[5];
int size = *(pArray-1);

funzionerà? In Windows e Linux non abbiamo funzionato.
amico

1
prova size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)invece

9

deleteo delete[]probabilmente libererebbe entrambi la memoria allocata (memoria puntata), ma la grande differenza è che deletesu un array non si chiamerà il distruttore di ciascun elemento dell'array.

Comunque, mescolando new/new[]ed delete/delete[]è probabilmente UB.


1
Risposta chiara, breve e la più utile!
GntS,

6

Non sa che è un array, ecco perché devi fornire delete[]invece dei vecchi regolari delete.


5

Ho avuto una domanda simile a questa. In C, allochi la memoria con malloc () (o un'altra funzione simile), e la elimini con free (). C'è solo un malloc (), che alloca semplicemente un certo numero di byte. C'è solo un free (), che prende semplicemente un puntatore come parametro.

Quindi perché in C puoi semplicemente consegnare il puntatore per liberarlo, ma in C ++ devi dire se si tratta di un array o di una singola variabile?

La risposta, ho imparato, ha a che fare con i distruttori di classe.

Se si assegna un'istanza di una classe MyClass ...

classes = new MyClass[3];

Ed eliminalo con delete, potresti ottenere il distruttore solo per la prima istanza di MyClass chiamata. Se si utilizza delete [], si può essere certi che il distruttore verrà chiamato per tutte le istanze dell'array.

Questa è la differenza importante. Se stai semplicemente lavorando con tipi standard (es. Int) non vedrai davvero questo problema. Inoltre, dovresti ricordare che il comportamento nell'uso di delete su new [] ed delete [] su new non è definito - potrebbe non funzionare allo stesso modo su ogni compilatore / sistema.


3

Spetta al runtime che è responsabile dell'allocazione della memoria, nello stesso modo in cui è possibile eliminare un array creato con malloc in C standard usando free. Penso che ogni compilatore lo implementa in modo diverso. Un modo comune è allocare una cella aggiuntiva per la dimensione dell'array.

Tuttavia, il runtime non è abbastanza intelligente da rilevare se si tratta di un array o di un puntatore, è necessario informarlo e, in caso di errore, o non si elimina correttamente (ad es. Ptr invece di array), oppure si finisce per prendere un valore non correlato per la dimensione e causare danni significativi.


3

Uno degli approcci per i compilatori è quello di allocare un po 'più di memoria e memorizzare il conteggio degli elementi nell'elemento head.

Esempio come potrebbe essere fatto: qui

int* i = new int[4];

il compilatore assegnerà sizeof (int) * 5 byte.

int *temp = malloc(sizeof(int)*5)

Memorizzerà 4nei primi sizeof(int)byte

*temp = 4;

e impostare i

i = temp + 1;

Quindi iindica una matrice di 4 elementi, non 5.

E

delete[] i;

verrà elaborato nel modo seguente

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

1

Semanticamente, entrambe le versioni dell'operatore delete in C ++ possono "mangiare" qualsiasi puntatore; tuttavia, se viene fornito un puntatore a un singolo oggetto delete[], si verificherà UB, il che significa che può succedere di tutto, incluso un arresto anomalo del sistema o nulla.

C ++ richiede al programmatore di scegliere la versione corretta dell'operatore di eliminazione in base all'oggetto della deallocazione: array o singolo oggetto.

Se il compilatore fosse in grado di determinare automaticamente se un puntatore passato all'operatore di eliminazione fosse un array di puntatori, allora ci sarebbe un solo operatore di eliminazione in C ++, che sarebbe sufficiente per entrambi i casi.


1

Accetto che il compilatore non sappia se è un array o no. Spetta al programmatore.

Il compilatore a volte tiene traccia di quanti oggetti devono essere eliminati assegnando una quantità eccessiva sufficiente per memorizzare le dimensioni dell'array, ma non sempre è necessario.

Per una specifica completa in caso di allocazione di spazio di archiviazione aggiuntivo, fare riferimento a ABI C ++ (come vengono implementati i compilatori): Itanium C ++ ABI: Array Operator new cookies


Vorrei solo che ogni compilatore osservasse un ABI documentato per C ++. +1 per il link che ho già visitato. Grazie.
Don Wakefield,

0

Non è possibile utilizzare delete per un array e non è possibile utilizzare delete [] per un non array.


8
Penso che intendi non dovrebbe , poiché il tuo compilatore medio non rileverà l'abuso.
Don Wakefield,

0

"comportamento indefinito" significa semplicemente che le specifiche del linguaggio non garantiscono ciò che accadrà. Non significa necessariamente che accadrà qualcosa di brutto.

Quindi di chi è il compito di ricordare queste cose? Il sistema operativo mantiene un tipo di record in background? (Voglio dire, mi rendo conto di aver iniziato questo post dicendo che ciò che accade è indefinito, ma il fatto è che lo scenario "uccidere follia" non accade, quindi quindi nel mondo pratico qualcuno sta ricordando.)

Di solito ci sono due livelli qui. Il gestore della memoria sottostante e l'implementazione C ++.

In generale, il gestore della memoria ricorderà (tra le altre cose) la dimensione del blocco di memoria allocato. Potrebbe essere più grande del blocco richiesto dall'implementazione di C ++. In genere il gestore della memoria memorizzerà i suoi metadati prima del blocco di memoria allocato.

L'implementazione C ++ generalmente ricorderà la dimensione dell'array solo se deve farlo per i propri scopi, in genere perché il tipo ha un distruttore non trivalente.

Quindi per i tipi con un banale distruttore l'implementazione di "delete" e "delete []" è in genere la stessa. L'implementazione C ++ passa semplicemente il puntatore al gestore della memoria sottostante. Qualcosa di simile a

free(p)

D'altra parte per i tipi con un distruttore non banale "delete" e "delete []" sono probabilmente diversi. "elimina" sarebbe simile a (dove T è il tipo a cui punta il puntatore)

p->~T();
free(p);

Mentre "delete []" sarebbe qualcosa di simile.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

-1

scorrere la matrice di oggetti e chiamare distruttore per ognuno di essi. Ho creato questo semplice codice che sovraccarica nuove espressioni [] ed elimina [] e fornisce una funzione modello per deallocare memoria e chiamare distruttore per ogni oggetto, se necessario:

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

-2

basta definire un distruttore all'interno di una classe ed eseguire il codice con entrambe le sintassi

delete pointer

delete [] pointer

in base all'output puoi trovare le soluzioni


usare delete [] quando si nuovo un tipo di array. ad esempio int * a = new int; int * b = new int [5]; cancella a; elimina [] b;
Lineesh K Mohan,

-3

La risposta:

int * pArray = new int [5];

int size = * (pArray-1);

Inserito sopra non è corretto e produce un valore non valido. "-1" conta gli elementi Su un sistema operativo Windows a 64 bit la dimensione del buffer corretta risiede nell'indirizzo Ptr - 4 byte

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.