Supponiamo di avere il seguente codice:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
È sicuro? O deve ptr
essere eseguito il cast char*
prima dell'eliminazione?
Supponiamo di avere il seguente codice:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
È sicuro? O deve ptr
essere eseguito il cast char*
prima dell'eliminazione?
Risposte:
Dipende dalla "sicurezza". Di solito funzionerà perché le informazioni vengono memorizzate insieme al puntatore sull'allocazione stessa, quindi il deallocatore può riportarle nel posto giusto. In questo senso è "sicuro" fintanto che l'allocatore utilizza tag di confine interni. (Molti lo fanno.)
Tuttavia, come accennato in altre risposte, l'eliminazione di un puntatore void non chiamerà i distruttori, il che può essere un problema. In questo senso, non è "sicuro".
Non c'è una buona ragione per fare quello che stai facendo nel modo in cui lo stai facendo. Se si desidera scrivere le proprie funzioni di deallocazione, è possibile utilizzare i modelli di funzione per generare funzioni con il tipo corretto. Un buon motivo per farlo è generare allocatori di pool, che possono essere estremamente efficienti per tipi specifici.
Come accennato in altre risposte, questo è un comportamento indefinito in C ++. In generale è bene evitare comportamenti indefiniti, sebbene l'argomento stesso sia complesso e pieno di opinioni contrastanti.
sizeof(T*) == sizeof(U*)
per tutti T,U
suggerisce che dovrebbe essere possibile avere 1 void *
implementazione di garbage collector basata su modelli non . Ma poi, quando il gc deve effettivamente eliminare / liberare un puntatore, sorge esattamente questa domanda. Per farlo funzionare, o hai bisogno di wrapper distruttori di funzioni lambda (urgh) o avresti bisogno di una sorta di tipo dinamico "tipo come dati" che consenta di andare avanti e indietro tra un tipo e qualcosa di memorizzabile.
L'eliminazione tramite un puntatore void non è definita dallo standard C ++ - vedere la sezione 5.3.5 / 3:
Nella prima alternativa (elimina oggetto), se il tipo statico dell'operando è diverso dal suo tipo dinamico, il tipo statico deve essere una classe base del tipo dinamico dell'operando e il tipo statico deve avere un distruttore virtuale o il comportamento è indefinito . Nella seconda alternativa (elimina array) se il tipo dinamico dell'oggetto da eliminare è diverso dal suo tipo statico, il comportamento è indefinito.
E la sua nota a piè di pagina:
Ciò implica che un oggetto non può essere cancellato utilizzando un puntatore di tipo void * perché non ci sono oggetti di tipo void
.
NULL
fare qualche differenza per la gestione della memoria dell'applicazione?
Non è una buona idea e non è qualcosa che faresti in C ++. Stai perdendo le informazioni sul tipo senza motivo.
Il tuo distruttore non verrà chiamato sugli oggetti nel tuo array che stai eliminando quando lo chiami per tipi non primitivi.
Dovresti invece sovrascrivere new / delete.
Eliminare il vuoto * probabilmente libererà la memoria correttamente per caso, ma è sbagliato perché i risultati non sono definiti.
Se per qualche motivo a me sconosciuto hai bisogno di memorizzare il tuo puntatore in un vuoto * quindi liberalo, dovresti usare malloc e free.
La domanda non ha senso. La tua confusione potrebbe essere in parte dovuta al linguaggio sciatto con cui le persone usano spessodelete
:
Si usa delete
per distruggere un oggetto che è stato allocato dinamicamente. In questo modo, formerai un'espressione di cancellazione con un puntatore a quell'oggetto . Non "cancelli mai un puntatore". Quello che fai veramente è "eliminare un oggetto identificato dal suo indirizzo".
Ora vediamo perché la domanda non ha senso: un puntatore vuoto non è "l'indirizzo di un oggetto". È solo un indirizzo, senza semantica. Essa può provenire 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 a un oggetto è restituire il puntatore a un oggetto (il che richiede all'autore di sapere cosa significa il puntatore). void
esso stesso è un tipo incompleto e quindi mai il tipo di un oggetto, e un puntatore void non può mai essere usato per identificare un oggetto. (Gli oggetti sono identificati congiuntamente dal tipo e dal loro indirizzo.)
delete
può essere un valore di puntatore nullo, un puntatore a un oggetto non array creato da una nuova espressione precedente o un puntatore a un oggetto secondario che rappresenta una classe base di tale oggetto. In caso contrario, il comportamento non è definito. " Quindi, se un compilatore accetta il codice senza una diagnostica, non è altro che un bug nel compilatore ...
delete void_pointer
. È un comportamento indefinito. I programmatori non dovrebbero mai invocare un comportamento indefinito, anche se la risposta sembra fare ciò che il programmatore voleva che fosse fatto.
Se davvero deve fare questo, perché non tagliare fuori l'uomo medio (le new
e delete
operatori) e chiamare il globale operator new
e operator delete
direttamente? (Ovviamente, se stai cercando di strumentare gli operatori new
e delete
, in realtà dovresti reimplementare operator new
e operator delete
.)
void* my_alloc (size_t size)
{
return ::operator new(size);
}
void my_free (void* ptr)
{
::operator delete(ptr);
}
Nota che a differenza di malloc()
, operator new
genera std::bad_alloc
in caso di errore (o chiama il new_handler
se uno è registrato).
Perché char non ha una logica distruttore speciale. QUESTO non funzionerà.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
L'autore non verrà chiamato.
Se vuoi usare void *, perché non usi solo malloc / free? new / delete è più della semplice gestione della memoria. Fondamentalmente, new / delete chiama un costruttore / distruttore e ci sono più cose in corso. Se usi solo i tipi incorporati (come char *) e li elimini tramite void *, funzionerebbe ma comunque non è raccomandato. La linea di fondo è usare malloc / free se vuoi usare void *. Altrimenti, puoi utilizzare le funzioni del modello per tua comodità.
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);
}
Molte persone hanno già commentato dicendo che no, non è sicuro eliminare un puntatore vuoto. Sono d'accordo con questo, ma volevo anche aggiungere che se stai lavorando con puntatori void per allocare array contigui o qualcosa di simile, puoi farlo in new
modo da poterlo usare in delete
sicurezza (con, ahem , un po 'di lavoro extra). Questo viene fatto assegnando un puntatore vuoto alla regione di memoria (chiamata "arena") e quindi fornendo il puntatore all'arena a new. Vedere questa sezione nelle domande frequenti su C ++ . Questo è un approccio comune all'implementazione dei pool di memoria in C ++.
Non c'è quasi un motivo per farlo.
Prima di tutto, se non conosci il tipo di dati, e tutto quello che sai è che lo è void*
, allora dovresti davvero trattare quei dati come un blob senza tipo di dati binari ( unsigned char*
) e usare malloc
/ free
per gestirli . Questo è necessario a volte per cose come i dati delle forme d'onda e simili, dove è necessario passarevoid*
puntatori alle API C. Va bene.
Se fai conoscere il tipo di dati (cioè ha un ctor / dtor), ma per qualche ragione si è conclusa con un void*
puntatore (per qualsiasi motivo avete) allora si dovrebbe davvero gettato di nuovo al tipo di sapere che a essere , e chiamalo delete
.
Ho usato void *, (aka tipi sconosciuti) nel mio framework per la riflessione sul codice e altre imprese di ambiguità, e finora non ho avuto problemi (perdita di memoria, violazioni di accesso, ecc.) Da nessun compilatore. Solo avvertimenti dovuti al fatto che l'operazione non è standard.
Ha perfettamente senso eliminare uno sconosciuto (void *). Assicurati solo che il puntatore segua queste linee guida, altrimenti potrebbe smettere di avere senso:
1) Il puntatore sconosciuto non deve puntare a un tipo che ha un decostruttore banale, quindi quando lanciato come puntatore sconosciuto non dovrebbe MAI ESSERE ELIMINATO. Elimina il puntatore sconosciuto solo DOPO averlo ripristinato nel tipo ORIGINALE.
2) L'istanza viene referenziata come puntatore sconosciuto nella memoria associata allo stack o all'heap? Se il puntatore sconosciuto fa riferimento a un'istanza sullo stack, non dovrebbe MAI ESSERE ELIMINATO!
3) Sei sicuro al 100% che il puntatore sconosciuto sia una regione di memoria valida? No, allora non dovrebbe MAI ESSERE DELTATO!
In tutto, c'è pochissimo lavoro diretto che può essere fatto usando un tipo di puntatore sconosciuto (void *). Tuttavia, indirettamente, il vuoto * è una grande risorsa per gli sviluppatori C ++ su cui fare affidamento quando è richiesta l'ambiguità dei dati.
Se vuoi solo un buffer, usa malloc / free. Se devi usare new / delete, considera una banale classe wrapper:
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 del char.
char è un tipo intrinseco che non dispone di un distruttore speciale. Quindi l'argomento delle fughe di notizie è discutibile.
sizeof (char) è di solito uno quindi non ci sono nemmeno argomenti di allineamento. Nel caso di piattaforme rare in cui la dimensione di (char) non è una, allocano la memoria sufficientemente allineata per il loro carattere. Quindi anche l'argomento dell'allineamento è discutibile.
malloc / free sarebbe più veloce in questo caso. Ma perdi std :: bad_alloc e devi controllare il risultato di malloc. Chiamare gli operatori globali new e delete potrebbe essere migliore in quanto aggira l'intermediario.
new
realtà sia definito da lanciare. Questo non è vero. Dipende dal compilatore e dal commutatore del compilatore. Vedi ad esempio gli /GX[-] enable C++ EH (same as /EHsc)
switch MSVC2019 . Anche sui sistemi embedded molti scelgono di non pagare le tasse sulle prestazioni per le eccezioni C ++. Quindi la frase che inizia con "But you forfeit std :: bad_alloc ..." è discutibile.