Supongamos que tengo el siguiente código:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
¿Es esto seguro? ¿O se debe ptr
convertir char*
antes de eliminarlo?
Supongamos que tengo el siguiente código:
void* my_alloc (size_t size)
{
return new char [size];
}
void my_free (void* ptr)
{
delete [] ptr;
}
¿Es esto seguro? ¿O se debe ptr
convertir char*
antes de eliminarlo?
Respuestas:
Depende de "seguro". Por lo general, funcionará porque la información se almacena junto con el puntero sobre la asignación en sí, por lo que el desasignador puede devolverla al lugar correcto. En este sentido, es "seguro" siempre que su asignador utilice etiquetas de límites internas. (Muchos hacen.)
Sin embargo, como se mencionó en otras respuestas, eliminar un puntero vacío no llamará a los 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 forma en 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 grupos, que pueden ser extremadamente eficientes para tipos específicos.
Como se mencionó en otras respuestas, este es un comportamiento indefinido en C ++. En general es bueno evitar comportamientos indefinidos, aunque el tema en sí es complejo y está lleno de opiniones encontradas.
sizeof(T*) == sizeof(U*)
para todos T,U
sugiere que debería ser posible tener 1 void *
implementación de recolector de basura basada en plantillas . Pero luego, cuando el gc realmente tiene que eliminar / liberar un puntero, surge exactamente esta pregunta. Para que funcione, o necesita envoltorios destructores de función lambda (urgh) o necesitaría algún tipo de "tipo como datos" dinámico que permita ir y venir entre un tipo y algo almacenable.
La eliminación mediante un puntero vacío no está definida por el estándar 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 dinámico, el tipo estático será una clase base del tipo dinámico del operando y el tipo estático tendrá un destructor virtual o el comportamiento no está definido . En la segunda alternativa (eliminar matriz) si el tipo dinámico 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 usando un puntero de tipo void * porque no hay objetos de tipo void
.
NULL
alguna diferencia en la gestión de la memoria de la aplicación?
No es una buena idea y no es algo que haría en C ++. Estás perdiendo la información de tu tipo sin ningún motivo.
No se llamará a su destructor en los objetos en su matriz que está eliminando cuando lo llame para tipos no primitivos.
En su lugar, debe anular nuevo / eliminar.
Eliminar el vacío * probablemente liberará su 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 libérelo, debe usar malloc y free.
La pregunta no tiene sentido. Su confusión puede deberse en parte al lenguaje descuidado que la gente usa con frecuencia delete
:
Usas delete
para destruir un objeto que fue asignado 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 identificado por su dirección".
Ahora vemos por qué la pregunta no tiene sentido: un puntero vacío no es la "dirección de un objeto". Es solo una dirección, sin semántica. Se puede haber venido de la dirección de un objeto real, pero se pierde esa información, ya que fue codificada en el tipo del puntero originales. La única forma de restaurar un puntero de objeto es devolver el puntero vacío a un puntero de objeto (lo que requiere que el autor sepa lo que significa el puntero). void
en sí mismo es un tipo incompleto y, por lo tanto, nunca el tipo de un objeto, y un puntero vacío nunca puede usarse para identificar un objeto. (Los objetos se identifican conjuntamente por su tipo y su dirección).
delete
puede ser un valor de puntero nulo, un puntero a un objeto que no es una matriz creado por una nueva expresión anterior o un puntero a un subobjeto que representa una clase base de dicho objeto. De lo contrario, el comportamiento no está definido ". Entonces, si un compilador acepta su código sin un diagnóstico, no es más que un error en el compilador ...
delete void_pointer
. Es un comportamiento indefinido. Los programadores nunca deben invocar un comportamiento indefinido, incluso si la respuesta parece hacer lo que el programador quería que se hiciera.
Si realmente tiene que hacer esto, ¿por qué no cortar al hombre medio (las new
y delete
los operadores) y llamar a lo global operator new
y operator delete
directa? (Por supuesto, si está tratando de instrumentar los operadores new
y delete
, en realidad debería volver a implementar 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 malloc()
, a diferencia de , operator new
arroja std::bad_alloc
fallos (o llama al new_handler
si hay uno registrado).
Porque char no tiene una lógica destructora especial. ESTO no funcionará.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
No llamarán al d'ctor.
Si desea usar void *, ¿por qué no usa solo malloc / free? new / delete es más que solo administrar la memoria. Básicamente, new / delete llama a un constructor / destructor y están sucediendo más cosas. Si solo usa tipos integrados (como char *) y los elimina a través de void *, funcionaría, pero aún así no se recomienda. La conclusión es usar malloc / free si desea usar void *. De lo contrario, puede utilizar funciones de plantilla para su conveniencia.
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);
}
Mucha gente ya ha comentado diciendo 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 new
para que pueda usarlo de delete
manera segura (con, ejem , un poco de trabajo extra). Esto se hace asignando un puntero vacío a la región de memoria (llamado 'arena') y luego suministrando el puntero a la arena a new. Consulte esta sección en las preguntas frecuentes de C ++ . Este es un enfoque común para implementar grupos de memoria en C ++.
Difícilmente hay una razón para hacer esto.
En primer lugar, si no conoce el tipo de datos, y todo lo que sabe es que lo es void*
, entonces realmente debería tratar esos datos como un blob sin tipo de datos binarios ( unsigned char*
) y usar malloc
/ free
para tratarlos. . Esto es necesario a veces para cosas como datos de forma de onda y similares, donde necesita pasarvoid*
punteros a C apis. Esta bien.
Si lo hace saber el tipo de los datos (es decir, tiene una ctor / dtor), pero por alguna razón que terminó con un void*
puntero (por cualquier razón que tenga) entonces usted realmente debería echarlo hacia atrás con el tipo que te des cuenta de sé , y llámalo delete
.
He usado void *, (también conocido como tipos desconocidos) en mi marco durante la reflexión de código y otras hazañas de ambigüedad, y hasta ahora, no he tenido problemas (pérdida de memoria, violaciones de acceso, etc.) de ningún compilador. Solo advertencias debido a que el funcionamiento no es estándar.
Tiene mucho sentido eliminar un desconocido (vacío *). Solo asegúrese 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 lanza como puntero desconocido NUNCA SE DEBE BORRAR. Solo elimine el puntero desconocido DESPUÉS de convertirlo de nuevo en el tipo ORIGINAL.
2) ¿Se hace referencia a la instancia como un puntero desconocido en la memoria enlazada a la pila o al montón? Si el puntero desconocido hace referencia a una instancia en la pila, ¡NUNCA SE DEBE BORRAR!
3) ¿Está 100% seguro de que el puntero desconocido es una región de memoria válida? No, ¡NUNCA SE DEBE BORRAR!
En total, hay muy poco trabajo directo que se pueda hacer usando un tipo de puntero desconocido (vacío *). Sin embargo, indirectamente, el vacío * es un gran activo en el que los desarrolladores de C ++ pueden confiar cuando se requiere ambigüedad de datos.
Si solo desea un búfer, use malloc / free. Si debe usar new / delete, considere una clase contenedora trivial:
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;
Para el caso particular de char.
char es un tipo intrínseco que no tiene un destructor especial. Así que los argumentos de las filtraciones son discutibles.
sizeof (char) suele ser uno, por lo que tampoco hay un argumento de alineación. En el caso de una plataforma rara donde el tamaño de (char) no es uno, asignan memoria alineada lo suficiente para su char. Así que el argumento de la alineación también es discutible.
malloc / free sería más rápido en este caso. Pero pierde std :: bad_alloc y tiene que verificar el resultado de malloc. Llamar a los operadores globales nuevos y de eliminación podría ser mejor, ya que evita al intermediario.
new
realidad está definido lanzar. Esto no es verdad. Depende del compilador y del modificador del compilador. Consulte, por ejemplo, /GX[-] enable C++ EH (same as /EHsc)
conmutadores MSVC2019 . También en los sistemas integrados, muchos eligen no pagar los impuestos sobre el rendimiento de las excepciones de C ++. Entonces, la oración que comienza con "Pero pierdes std :: bad_alloc ..." es cuestionable.