E 'sicuro per eliminare un puntatore nullo?
-
06-09-2019 - |
Domanda
Supponiamo che io ho il seguente codice:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
Questa funzione è sicura? O deve ptr
essere gettato a char*
prima eliminazione?
Soluzione
Dipende "sicuro". E di solito funziona perché le informazioni sono memorizzate insieme con il puntatore sulla allocazione in sé, in modo che il deallocatore può tornare al posto giusto. In questo senso è "sicuro" fino a quando l'allocatore utilizza tag di confine interne. (Molti lo fanno.)
Tuttavia, come detto in altre risposte, l'eliminazione di un puntatore nullo non sarà chiamare i distruttori, che può essere un problema. In questo senso, non è "sicuro".
Non v'è alcuna buona ragione per fare quello che stai facendo il modo in cui si sta facendo. Se si desidera scrivere le proprie funzioni deallocazione, è possibile utilizzare i modelli di funzione per generare funzioni con il tipo corretto. Un buon motivo per farlo è quello di generare allocatori piscina, che possono essere estremamente efficiente per tipi specifici.
Come già detto in altre risposte, questo è indefinito comportamento in C ++. In generale, è bene evitare un comportamento indefinito, anche se l'argomento in sé è complesso e pieno di opinioni contrastanti.
Altri suggerimenti
L'eliminazione tramite un puntatore nullo non è definito dal ++ standard C - vedi sezione 5.3.5 / 3:
Nella prima alternativa (cancellare oggetto), se il tipo statico del operando è diverso dal suo dinamico tipo, tipo statico è una base classe di tipo dinamico della operando e il tipo statico deve avere una distruttore virtuale o il comportamento è non definito. Nella seconda alternativa (Cancella array) se il tipo dinamico di l'oggetto da eliminare differisce dalla il tipo statico, il comportamento è non definito.
E la sua nota:
Ciò implica che un oggetto non può essere eliminato utilizzando un puntatore di tipo void * perché non ci sono oggetti di tipo vuoto
.
Non è una buona idea e non qualcosa che si farebbe in C ++. Si stanno perdendo le tue informazioni tipo per nessun motivo.
Il distruttore non sarà chiamato sugli oggetti nel vostro array che si sta Cancellazione quando lo si chiama per i tipi non primitivi.
Si dovrebbe invece ignorare nuova / delete.
L'eliminazione del vuoto * probabilmente liberare la memoria in modo corretto per caso, ma è sbagliato, perché i risultati sono indefiniti.
Se per qualche motivo a me sconosciuto è necessario memorizzare il puntatore in un vuoto * poi liberarla, si dovrebbe usare malloc e gratuito.
L'eliminazione di un puntatore nullo è pericoloso perché non distruttori saranno chiamati sul valore in realtà punta a. Ciò può causare perdite di memoria / risorsa nell'applicazione.
La domanda non ha senso. La vostra confusione può essere in parte dovuto al popolo lingua sciatta utilizzano spesso con delete
:
Si utilizza delete
per distruggere un oggetto che è stato assegnato dinamicamente. Non fare così, si forma un'espressione Elimina con puntatore a tale oggetto . Non si può mai "elimina un puntatore". Quello che è davvero fare è "eliminare un oggetto, che è identificato dal suo indirizzo".
Ora vediamo il motivo per cui la questione non ha senso: Un puntatore nullo non è il "indirizzo di un oggetto". E 'solo un indirizzo, senza alcuna semantica. E ' possono sono venuti da l'indirizzo di un oggetto reale, ma che l'informazione è persa, perché è stato codificato nel tipo del puntatore originale. L'unico modo per ripristinare un puntatore oggetto è quello di lanciare il puntatore nullo di nuovo ad un puntatore di oggetto (che richiede l'autore di sapere che cosa significa il puntatore). void
sé un tipo incompleto e quindi mai il tipo di un oggetto, e un puntatore nullo può mai essere utilizzato per identificare un oggetto. (Gli oggetti sono identificati congiuntamente dal loro tipo e indirizzo.)
A causa char non ha alcuna logica speciale distruttore. Questo non funziona.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
Il d'ctor non verrà chiamato.
Se si desidera utilizzare * vuoto, perché non si utilizza solo malloc / libero? new / delete è più di una semplice gestione della memoria. In sostanza, new / delete chiamate un costruttore / distruttore e ci sono più cose in corso. Se si utilizza solo tipi built-in (come char *) e li elimina attraverso void *, che avrebbe funzionato, ma ancora non è raccomandato. La linea di fondo è l'uso malloc / gratuito se si desidera utilizzare void *. In caso contrario, è possibile utilizzare le funzioni di template per la vostra convenienza.
template<typename T>
T* my_alloc (size_t size)
{
return new T [size];
}
template<typename T>
void my_free (T* ptr)
{
delete [] ptr;
}
int main(void)
{
char* pChar = my_alloc<char>(10);
my_free(pChar);
}
Se proprio deve fare questo, perché non tagliare fuori l'uomo medio (il new
e operatori delete
) e chiamare il operator new
globale e operator delete
direttamente? (Naturalmente, se si sta cercando di strumento gli operatori new
e delete
, in realtà si dovrebbe reimplementare operator new
e operator delete
.)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
Si noti che a differenza malloc()
, operator new
getta std::bad_alloc
in caso di fallimento (o chiama il new_handler
se si è registrato).
Un sacco di persone hanno già commentato dicendo che no, non è sicuro per eliminare un puntatore nullo. Sono d'accordo con questo, ma volevo anche aggiungere che se si sta lavorando con puntatori void al fine di allocare le matrici contigui o qualcosa di simile, che si può fare questo con new
in modo che sarete in grado di utilizzare in modo sicuro delete
(con , ahem, un po 'di lavoro extra). Questo viene fatto assegnando un puntatore nullo alla regione di memoria (chiamato 'un'arena') e quindi fornendo il puntatore al campo per nuovo. Vedere questa sezione nella C ++ FAQ . Questo è un approccio comune per attuare pool di memoria in C ++.
E 'difficile trovare una ragione per farlo.
Prima di tutto, se non si conosce il Tipo dei dati, e tutto quello che sappiamo è che si tratta di void*
, allora si dovrebbe davvero essere solo trattando i dati come un senza tipo blob di dati binari (unsigned char*
), e utilizzare malloc
/ free
a che fare con esso. Questo è necessario a volte per cose come i dati delle forme d'onda e simili, in cui è necessario passare intorno puntatori void*
a C API. Questo va bene.
Se fare conoscere il tipo di dati (cioè ha un ctor / dtor), ma per qualche ragione si è conclusa con un puntatore void*
(per qualsiasi motivo avete) allora si dovrebbe davvero gettato di nuovo al tipo di sapere che sia , e chiamare delete
su di esso.
Ho usato void *, (aka tipi sconosciuti) nel mio quadro, mentre nel codice riflessione e di altri talenti di ambiguità, e finora, non ho avuto problemi (perdita di memoria, le violazioni di accesso, etc.) da qualsiasi compilatori . Solo gli avvertimenti a causa del funzionamento di essere non standard.
Si rende perfettamente senso per eliminare uno sconosciuto (void *). Basta assicurarsi che il puntatore segue queste linee guida, o può smettere di dare un senso:
1) Il puntatore ignoto non deve puntare ad un tipo che ha una deconstructor banale, e così quando fuso come un puntatore Sconosciuto dovrebbe mai eliminati. eliminare il puntatore sconosciuta Solo dopo la fusione di nuovo nel tipo di originale.
2) è l'istanza viene fatto riferimento come un puntatore sconosciuta nella memoria dello stack legato o heap legato? Se il puntatore sconosciuto fa riferimento a un'istanza in pila, quindi non dovrebbe mai essere cancellato!
3) Sei positivo di 100% il puntatore ignoto è una regione di memoria valida? No, allora non dovrebbe mai essere DELTED!
In tutto, c'è molto poco lavoro diretto che può essere fatto utilizzando un ignoto (void *) tipo di puntatore. Tuttavia, indirettamente, il vuoto * è una grande risorsa per gli sviluppatori C ++ di fare affidamento su quando è richiesta l'ambiguità dei dati.
Se si desidera solo un buffer, utilizzare malloc / libero. Se è necessario utilizzare nuovi / delete, prendere in considerazione una classe wrapper banale:
template<int size_ > struct size_buffer {
char data_[ size_];
operator void*() { return (void*)&data_; }
};
typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer
OpaqueBuffer* ptr = new OpaqueBuffer();
delete ptr;
Per il caso particolare di carattere.
char è un tipo intrinseco che non ha un distruttore speciale. Così gli argomenti perdite è un controverso.
sizeof (char) è di solito uno così non c'è argomento allineamento neanche. Nel caso di rara piattaforma in cui il sizeof (char) è non uno, allocano memoria allineato abbastanza per la loro char. Quindi l'argomento di allineamento è anche una discutibile.
malloc / libero sarebbe più veloce su questo caso. Ma si perde std :: bad_alloc e hanno per controllare il risultato di malloc. Chiamare gli operatori new e delete globali potrebbe essere migliore in quanto bypassare l'uomo medio.