Domanda

Bene, penso che siamo tutti d'accordo 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 un sacco di cose diverse, e quindi l'esecuzione di un delete[] incondizionata su di esso non è definito. Tuttavia, supponiamo che siamo davvero passando un puntatore,

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

La mia domanda è, in questo caso in cui il puntatore del un array, chi è che lo sa? Voglio dire, dal punto di vista del linguaggio / compilatore, non ha idea se o non arr è un puntatore rispetto a un puntatore ad un singolo int. Diamine, non sa nemmeno se arr è stato creato in modo dinamico. Eppure, se faccio il seguente, invece,

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

Il sistema operativo è abbastanza intelligente per eliminare soltanto un int e non andare su un certo tipo di 'follia omicida' eliminando il resto della memoria oltre quel punto (contrasto che con strlen e una stringa non \0-terminato - è andrà avanti fino a quando non colpisce 0).

Quindi, il cui compito è per ricordare queste cose? Ha il sistema operativo di tenere un certo tipo di record in background? (Voglio dire, mi rendo conto che ho iniziato questo post dicendo che ciò che accade non è definito, ma il fatto è, lo scenario 'follia omicida' non accade, in modo da quindi nel mondo pratico qualcuno è ricordare.)

È stato utile?

Soluzione

Il compilatore non sa che è un array, è fidarsi del programmatore. L'eliminazione di un puntatore ad un singolo int con delete [] si tradurrebbe in un comportamento indefinito. Il tuo secondo esempio main() non è sicuro, anche se non va in crash immediatamente.

Il compilatore non deve tenere traccia di quanti oggetti devono essere eliminati in qualche modo. Essa può fare questo da un eccesso di assegnazione sufficiente per memorizzare la dimensione della matrice. Per maggiori dettagli, vedere la C ++ Super FAQ .

Altri suggerimenti

Una domanda che le risposte date finora non sembrano affrontare: se le librerie di runtime (non il sistema operativo, in realtà) possono tenere traccia del numero di cose nella matrice, allora perché abbiamo bisogno la sintassi delete[] a tutti? Perché non è possibile una sola forma delete essere utilizzato per gestire tutte le eliminazioni?

La risposta a questo risale alle radici C ++ s 'come un linguaggio C-compatibile (che non è più veramente si sforza di essere.) La filosofia di Stroustrup era che il programmatore non dovrebbe pagare per tutte le funzioni che non stanno usando. Se non sono l'utilizzo di matrici, quindi non dovrebbero avere a portare il costo degli array di oggetti per ogni pezzo allocata della memoria.

Cioè, se il codice fa semplicemente

Foo* foo = new Foo;

quindi lo spazio di memoria che è allocata per foo non dovrebbe includere alcun overhead in più che sarebbe necessaria per supportare le matrici di Foo.

Dal momento che solo assegnazioni di matrice sono impostati in modo da portare le informazioni aggiuntive dimensione della matrice, è quindi necessario dire le librerie di runtime per cercare le informazioni quando si eliminano gli oggetti. Ecco perché abbiamo bisogno di usare

delete[] bar;

invece di

delete bar;

se bar è un puntatore ad un array.

Per la maggior parte di noi (me compreso), che pignoleria di un paio di byte in più di memoria sembra caratteristico di questi tempi. Ma ci sono ancora alcune situazioni in cui il salvataggio di un paio di byte (da quello che potrebbe essere un numero molto elevato di blocchi di memoria) può essere importante.

Sì, il sistema operativo mantiene alcune cose in 'background'. Ad esempio, se si esegue

int* num = new int[5];

il sistema operativo può allocare 4 byte aggiuntivi, memorizzare la dimensione della dotazione nei primi 4 byte della memoria allocata e restituire un puntatore offset (cioè, assegna spazi di memoria 1000-1024 ma il puntatore restituito punti a 1004, con località 1000-1003 memorizzare la dimensione della dotazione). Poi, quando si chiama eliminazione, si può guardare a 4 byte prima che il puntatore passato ad esso per trovare la dimensione della dotazione.

Sono sicuro che ci sono altri modi di monitoraggio delle dimensioni di uno stanziamento, ma questa è una possibilità.

Questo è molto simile a questo domanda ed ha molti dei dettagli che state cercando.

Ma basti dire, non è il lavoro del sistema operativo per tenere traccia di tutto questo. In realtà le librerie runtime o sottostante gestore di memoria che traccia la dimensione della matrice. Questo è solitamente fatto allocando memoria aggiuntiva su fronte e memorizzare la dimensione della matrice in quella posizione (più utilizzare un nodo testa).

Questo è visibile in alcune implementazioni eseguendo il seguente codice

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

delete o delete[] probabilmente sia liberare la memoria allocata (memory sottolineato), ma la grande differenza è che delete su un array non chiamerà il distruttore di ciascun elemento della matrice.

In ogni caso, new/new[] e delete/delete[] miscelazione è probabilmente UB.

Non sapere che è un array, è per questo che è necessario fornire delete[] invece di regolare vecchi delete.

Ho avuto una domanda simile a questo. In C, si alloca memoria con malloc () (o un'altra funzione simile), ed eliminare con free (). C'è solo una malloc (), che assegna semplicemente un certo numero di byte. C'è solo un free (), che prende semplicemente un puntatore come è parametro.

Allora perché è che in C si può semplicemente consegnare il puntatore per liberare, ma in C ++ si deve dire che se si tratta di un array o di una singola variabile?

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

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

classes = new MyClass[3];

E eliminarlo con eliminazione, si può solo ottenere il distruttore per la prima istanza di MyClass chiamato. Se si utilizza delete [], si può essere certi che il distruttore sarà chiamato per tutte le istanze della matrice.

Questa è la differenza importante. Se si sta semplicemente lavorando con i tipi standard (ad esempio int) non sarà davvero vedere questo problema. Inoltre, si dovrebbe ricordare che il comportamento per l'utilizzo di eliminare il nuovo [] e delete [] sulla nuova non è definito -. Potrebbe non funzionare allo stesso modo su ogni compilatore / Sistema

E 'fino al runtime che è responsabile per l'allocazione di memoria, nello stesso modo in cui è possibile eliminare una matrice creata con malloc in C standard utilizzando gratuito. Credo che ogni compilatore implementa in modo diverso. Un modo comune è quello di allocare una cella supplementare per la dimensione dell'array.

Tuttavia, il runtime non è in grado di rilevare se sia o non sia un array o un puntatore, si deve comunicare, e se si sbaglia, si sia non eliminare correttamente (Ad esempio, invece di serie PTR ), o si finisce per prendere un valore non correlato per le dimensioni e causare danni significativi.

uno degli approcci per compilatori è allocare un po 'più memoria e memorizzare conteggio degli elementi dell'elemento testa.

Esempio di come potrebbe essere fatto: Qui

int* i = new int[4];

compilatore allocherà sizeof (int) * 5 byte.

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

Sarà memorizzare 4 nei primi byte sizeof(int)

*temp = 4;

e impostare i

i = temp + 1;

Così punti i a array di 4 elementi, non 5.

E

delete[] i;

saranno trattati seguente modo

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)

semantico, entrambe le versioni di operatore delete in C ++ in grado di "mangiare" qualsiasi puntatore; tuttavia, se un puntatore a un singolo oggetto è dato a delete[], allora UB si tradurrà, che significa tutto ciò può accadere, tra cui un crash di sistema o niente del tutto.

C ++ richiede al programmatore di scegliere la versione corretta del operatore delete a seconda del soggetto di deallocazione:. Array o un oggetto unico

Se il compilatore potrebbe determinare automaticamente se un puntatore passato per l'operatore delete era una matrice puntatore, allora ci sarebbe un solo operatore delete in C ++, che sarebbe sufficiente per entrambi i casi.

D'accordo che il compilatore non sa se si tratta di una matrice o meno. Spetta al programmatore.

Il compilatore a volte tenere traccia di quanti oggetti devono essere cancellati da un eccesso di assegnazione sufficiente per memorizzare la dimensione della matrice, ma non sempre necessario.

Per una specifica completa quando l'extra storage è allocato, si rimanda alla ABI C ++ (come i compilatori sono implementate): Itanium C ++ ABI: Array operatore nuovi cookie

Non è possibile utilizzare eliminare per una matrice, e non è possibile utilizzare delete [] per un non-array.

"comportamento non definito" significa semplicemente la specifica lingua non fa garanzia di qualitá da ciò che accadrà. Esso non nessacerally significa che qualcosa di brutto accadrà.

  

Quindi, il cui compito è per ricordare queste cose? Ha il sistema operativo di tenere un certo tipo di record in background? (Voglio dire, mi rendo conto che ho iniziato questo post dicendo che ciò che accade non è definito, ma il fatto è, lo scenario 'follia omicida' non accade, in modo da quindi nel mondo pratico qualcuno sta ricordando.)

Ci sono generalmente due strati qui. Il sottostante gestore di memoria e l'implementazione C ++.

In generale, il gestore di memoria ricorderà (tra le altre cose) la dimensione del blocco di memoria che è stato assegnato. Questo può essere più grande del blocco l'implementazione C ++ chiesto. In genere il gestore della memoria immagazzinerà è metadati prima del blocco allocato di memoria.

L'implementazione C ++ generalmente ricordare solo la dimensione della matrice se deve farlo per è propri scopi, tipicamente perché il tipo ha un distruttore non banale.

Quindi, per i tipi con un distruttore banale l'attuazione di "cancellare" e "delete []" è in genere lo stesso. Il C ++ attuazione passa semplicemente il puntatore il gestore di memoria sottostante. Qualcosa di simile

free(p)

D'altra parte per i tipi con un distruttore non banale "delete" e "delete []" sono suscettibili di essere diversi. "Delete" sarebbe somthing come (dove T è il tipo che i punti di 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);

Hey ho Beh, dipende di ciò che si allocare con nuova espressione [] quando si assegnano gamma di costruire in tipi o classe / struttura e non si fornisce il vostro costruttore e distruttore l'operatore trattarlo come una dimensione "sizeof ( oggetto) * numObjects" anziché matrice pertanto oggetto in questo numero caso di oggetti allocati non verranno memorizzati ovunque, se si alloca matrice di oggetti e di fornire costruttore e distruttore nell'oggetto di cambiamento comportamentale, nuova espressione allocherà 4 byte e memorizzare il numero di oggetti in primi 4 byte così il distruttore per ciascuno di essi può essere chiamato e quindi nuovo [] espressione restituisce puntatore spostata di 4 byte in avanti, rispetto a quando si restituisce la memoria delete [] espressione chiamerà un modello di funzione prima, scorrere array di oggetti e chiamare distruttore per ognuno di essi. Ho creato questo semplice codice strega sovraccarica new [] e delete [] espressioni e fornisce una funzione di modello per rilasciare la memoria e chiamare distruttore per ciascun 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 );
}
///////////////////////////////////////////

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

delete pointer

delete [] pointer

secondo l'uscita u può trovare le soluzioni

La risposta:

int * Parray = new int [5];

int size = * (Parray-1);

Inviato di cui sopra non è corretto e produce valore non valido.  Il "-1" conta elementi Su 64 bit del sistema operativo Windows la dimensione del buffer corretto risiede nel Ptr - 4 byte indirizzo

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