¿Es seguro eliminar un puntero vacío?

Supongamos que tengo el siguiente código:

void* my_alloc (size_t size) { return new char [size]; } void my_free (void* ptr) { delete [] ptr; } 

¿Esto es seguro? ¿O debe ptr ser lanzado a char* antes de la eliminación?

Depende de “seguro”. Por lo general, funcionará porque la información se almacena junto con el puntero sobre la asignación en sí misma, por lo que el desasignador puede devolverla al lugar correcto. En este sentido, es “seguro” siempre que su asignador utilice tags de límite internas. (Muchos hacen.)

Sin embargo, como se mencionó anteriormente, al eliminar un puntero vacío no se llamarán destructores, lo que puede ser un problema. En ese sentido, no es “seguro”.

No hay una buena razón para hacer lo que está haciendo de la manera que lo está haciendo. Si desea escribir sus propias funciones de desasignación, puede usar plantillas de funciones para generar funciones con el tipo correcto. Una buena razón para hacerlo es generar asignadores de grupo, que pueden ser extremadamente eficientes para tipos específicos.

Como se menciona en otras respuestas, este es un comportamiento indefinido en C ++. En general, es bueno evitar el comportamiento indefinido, aunque el tema en sí mismo es complejo y está lleno de opiniones contradictorias.

La eliminación a través de un puntero de vacío no está definida por el estándar de C ++; consulte la sección 5.3.5 / 3:

En la primera alternativa (eliminar objeto), si el tipo estático del operando es diferente de su tipo dynamic, el tipo estático debe ser una clase base del tipo dynamic del operando y el tipo estático debe tener un destructor virtual o el comportamiento no está definido . En la segunda alternativa (eliminar matriz) si el tipo dynamic del objeto a eliminar difiere de su tipo estático, el comportamiento no está definido.

Y su nota al pie:

Esto implica que un objeto no se puede eliminar utilizando un puntero de tipo void * porque no hay objetos de tipo void

.

No es una buena idea ni algo que harías en C ++. Está perdiendo su tipo de información sin ningún motivo.

No se invocará su destructor sobre los objetos en su matriz que está eliminando cuando lo llama para tipos no primitivos.

En su lugar, debe anular el nuevo / eliminar.

Eliminar el vacío * probablemente liberará la memoria correctamente por casualidad, pero está mal porque los resultados no están definidos.

Si por alguna razón desconocida para mí necesita almacenar su puntero en un vacío * y luego liberarlo, debe usar malloc y gratis.

Eliminar un puntero volador es peligroso porque no se invocarán destructores sobre el valor al que realmente apunta. Esto puede provocar memory leaks / recursos en su aplicación.

Porque char no tiene una lógica de destrucción especial. ESTO no funcionará

 class foo { ~foo() { printf("huzza"); } } main() { foo * myFoo = new foo(); delete ((void*)foo); } 

El d’ctor no será llamado.

Si desea usar void *, ¿por qué no usa solo malloc / free? new / delete es más que solo administración de memoria. Básicamente, new / delete llama a un constructor / destructor y hay más cosas que están pasando. Si solo usa tipos incorporados (como char *) y los elimina a través de void *, funcionaría, pero aún así no es recomendable. La conclusión es usar malloc / free si quieres usar void *. De lo contrario, puede usar funciones de plantilla para su conveniencia.

 template T* my_alloc (size_t size) { return new T [size]; } template void my_free (T* ptr) { delete [] ptr; } int main(void) { char* pChar = my_alloc(10); my_free(pChar); } 

Si realmente debe hacer esto, ¿por qué no cortar el intermediario (los operadores new y delete ) y llamar al operator new global operator new y operator delete directamente? (Por supuesto, si intentas instrumentar a los operadores new y delete , en realidad deberías volver a implementar el operator new y operator delete ).

 void* my_alloc (size_t size) { return ::operator new(size); } void my_free (void* ptr) { ::operator delete(ptr); } 

Tenga en cuenta que a diferencia de malloc() , el operator new arroja std::bad_alloc en el fallo (o llama al new_handler si hay uno registrado).

La pregunta no tiene sentido. Su confusión puede deberse en parte al lenguaje descuidado que la gente suele usar con delete :

Utiliza delete para destruir un objeto que se asignó dinámicamente. Hazlo, forma una expresión de eliminación con un puntero a ese objeto . Nunca “borras un puntero”. Lo que realmente hace es “eliminar un objeto que se identifica por su dirección”.

Ahora vemos por qué la pregunta no tiene sentido: un puntero de vacío no es la “dirección de un objeto”. Es solo una dirección, sin ninguna semántica. Puede haber provenido de la dirección de un objeto real, pero esa información se pierde porque estaba codificada en el tipo del puntero original. La única forma de restablecer un puntero de objeto es convertir el puntero de vacío en un puntero de objeto (lo que requiere que el autor sepa qué significa el puntero). void sí mismo es un tipo incompleto y, por lo tanto, nunca el tipo de un objeto, y un puntero vacío nunca se puede utilizar para identificar un objeto. (Los objetos se identifican conjuntamente por su tipo y su dirección).

Mucha gente ya comentó que no, no es seguro eliminar un puntero vacío. Estoy de acuerdo con eso, pero también quería agregar que si está trabajando con punteros vacíos para asignar matrices contiguas o algo similar, puede hacer esto con new para que pueda usar delete forma segura (con , ejem, un poco de trabajo extra). Esto se hace asignando un puntero vacío a la región de memoria (llamada ‘arena’) y luego suministrando el puntero a la arena a nuevo. Vea esta sección en las preguntas frecuentes de C ++ . Este es un enfoque común para implementar pools de memoria en C ++.

Apenas hay una razón para hacer esto.

En primer lugar, si no sabes el tipo de datos, y todo lo que sabes es que es void* , entonces realmente deberías tratar esos datos como una masa unsigned char* letra de datos binarios ( unsigned char* ), y usar malloc / free para manejarlo. Esto se requiere a veces para cosas como datos de forma de onda y similares, donde debe pasar punteros void* a C apis. Esta bien.

Si conoce el tipo de datos (es decir, tiene un ctor / dtor), pero por alguna razón terminó con un puntero void* (por la razón que tenga) entonces debería devolverlo al tipo que conoce ser y llamar a delete en él.

He utilizado void *, (también conocido como tipos desconocidos) en mi framework para reflexionar sobre el código y otras proezas de ambigüedad, y hasta ahora, no he tenido problemas (pérdida de memoria, violaciones de acceso, etc.) de ningún comstackdor. Solo las advertencias debido a que la operación no es estándar.

Tiene sentido borrar un desconocido (void *). Solo asegúrate de que el puntero siga estas pautas, o puede dejar de tener sentido:

1) El puntero desconocido no debe apuntar a un tipo que tenga un deconstructor trivial, por lo que cuando se lo arroje como un puntero desconocido, NUNCA DEBE ELIMINARSE. Solo elimine el puntero desconocido DESPUÉS de devolverlo al tipo ORIGINAL.

2) ¿Se hace referencia a la instancia como un puntero desconocido en la memoria enlazada a la stack o encuadernada en el montón? Si el puntero desconocido hace referencia a una instancia en la stack, ¡NUNCA DEBE BORRARSE!

3) ¿Es 100% positivo que el puntero desconocido sea una región de memoria válida? No, entonces NUNCA DEBERÍA SER DELTADO!

En total, hay muy poco trabajo directo que se puede hacer usando un tipo de puntero desconocido (void *). Sin embargo, indirectamente, el vacío * es un gran activo para los desarrolladores de C ++ para confiar cuando se requiere ambigüedad de datos.

Si solo quieres un buffer, usa malloc / free. Si debe usar new / delete, considere una clase de contenedor trivial:

 template 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; 

Para el caso particular de char.

char es un tipo intrínseco que no tiene un destructor especial. Entonces los argumentos de las filtraciones son discutibles.

sizeof (char) suele ser uno, por lo que tampoco hay argumento de alineación. En el caso de una plataforma rara donde el tamaño de (char) no es uno, asignan memoria lo suficientemente alineada para su char. Entonces el argumento de alineación también es discutible.

malloc / free sería más rápido en este caso. Pero pierdes std :: bad_alloc y tienes que verificar el resultado de malloc. Llamar a los operadores globales nuevos y eliminar podría ser mejor ya que omite al intermediario.