c++ - sintaxis - ¿Hay alguna razón para buscar un puntero NULL antes de eliminar?
punteros de char en c (10)
Creo que el desarrollador anterior lo codificó "redundantemente" para ahorrar algunos milisegundos: es bueno tener el puntero establecido en NULL al eliminarlo, por lo que podría usar una línea como la siguiente después de eliminar el objeto:
if(pSomeObject1!=NULL) pSomeObject1=NULL;
Pero, de todos modos, eliminar es hacer esa comparación exacta de todos modos (no hacer nada si es NULO). ¿Por qué hacer esto dos veces? Siempre puede asignar pSomeObject a NULL después de llamar a delete, independientemente de su valor actual, pero esto sería ligeramente redundante si ya tuviera ese valor.
Así que mi apuesta es que el autor de esas líneas intentó asegurarse de que pSomeObject1 siempre fuera NULL después de ser eliminado, sin incurrir en el costo de una prueba y asignación potencialmente innecesarias.
A menudo veo código heredado que comprueba NULL
antes de eliminar un puntero, similar a,
if (NULL != pSomeObject)
{
delete pSomeObject;
pSomeObject = NULL;
}
¿Hay alguna razón para buscar un puntero NULL
antes de eliminarlo? ¿Cuál es la razón para configurar el puntero a NULL
después?
De acuerdo con C ++ 03 5.3.5 / 2, es seguro eliminar un puntero nulo. Este siguiente es citado del estándar:
En cualquiera de las alternativas, si el valor del operando de eliminar es el puntero nulo, la operación no tiene efecto.
Depende de lo que está haciendo. Algunas implementaciones anteriores de free
, por ejemplo, no estarán contenidas si se les pasa un puntero NULL
. Algunas bibliotecas todavía tienen este problema. Por ejemplo, XFree
en la biblioteca Xlib dice:
DESCRIPCIÓN
La función XFree es una rutina Xlib de propósito general que libera los datos especificados. Debe usarlo para liberar cualquier objeto que haya sido asignado por Xlib, a menos que se especifique explícitamente una función alternativa para el objeto. Un puntero NULL no se puede pasar a esta función.
Así que considere liberar punteros NULL
como un error y estará a salvo.
El estándar de C ++ garantiza que es legal usar un puntero nulo en una expresión de eliminación (§8.5.2.5 / 2). Sin embargo, no se especifica si esto llamará a una función de desasignación ( operator delete
operator delete[]
o operator delete[]
; nota 8.5.5 / 7, nota).
Si se llama a una función de desasignación predeterminada (es decir, proporcionada por la biblioteca estándar) con un puntero nulo, entonces la llamada no tiene efecto (§6.6.4.4.2 / 3).
Pero no se especifica qué sucede si la biblioteca estándar no proporciona la función de desasignación, es decir, qué ocurre cuando sobrecargamos la operator delete
(o eliminamos el operator delete[]
).
Un programador competente manejaría los punteros nulos en consecuencia dentro de la función de desasignación, en lugar de antes de la llamada, como se muestra en el código de OP. Del mismo modo, establecer el puntero a nullptr
/ NULL
después de la eliminación solo tiene un propósito muy limitado. A algunas personas les gusta hacer esto en el espíritu de la programación defensiva : hará que el comportamiento del programa sea ligeramente más predecible en el caso de un error: acceder al puntero después de la eliminación dará como resultado un acceso nulo al puntero en lugar de un acceso a una ubicación de memoria aleatoria. Aunque ambas operaciones son un comportamiento indefinido, el comportamiento del acceso a un puntero nulo es mucho más predecible en la práctica (con frecuencia resulta en un bloqueo directo en lugar de corrupción de la memoria). Como la corrupción de la memoria es especialmente difícil de depurar, restablecer los punteros borrados ayuda a la eliminación de errores.
- Por supuesto, esto es tratar el síntoma en lugar de la causa (es decir, el error). Debe tratar los punteros de reinicio como el olor del código. Un código limpio y moderno de C ++ hará que la propiedad de la memoria sea clara y estética (mediante el uso de punteros inteligentes o mecanismos equivalentes), y así evitar esta situación.
Bonificación: una explicación de la operator delete
sobrecargado:
operator delete
es (a pesar de su nombre) una función que puede estar sobrecargada como cualquier otra función. Esta función se llama internamente para cada llamada de operator delete
del operator delete
con argumentos coincidentes. Lo mismo es cierto para el operator new
.
La sobrecarga del operator new
(y luego también la operator delete
) tiene sentido en algunas situaciones cuando se desea controlar con precisión cómo se asigna la memoria. Hacer esto ni siquiera es muy difícil, pero se deben tomar algunas precauciones para garantizar un comportamiento correcto. Scott Meyers describe esto con gran detalle Eficaz C ++ .
Por ahora, digamos que queremos sobrecargar la versión global del operator new
para la depuración. Antes de hacer esto, un breve aviso acerca de lo que sucede en el siguiente código:
klass* pobj = new klass;
// … use pobj.
delete pobj;
¿Qué sucede realmente aquí? Bueno, lo anterior se puede traducir aproximadamente al siguiente código:
// 1st step: allocate memory
klass* pobj = static_cast<klass*>(operator new(sizeof(klass)));
// 2nd step: construct object in that memory, using placement new:
new (pobj) klass();
// … use pobj.
// 3rd step: call destructor on pobj:
pobj->~klass();
// 4th step: free memory
operator delete(pobj);
Observe el paso 2 donde llamamos new
con una sintaxis un poco extraña. Esta es una llamada a la llamada colocación new
que toma una dirección y construye un objeto en esa dirección. Este operador también puede estar sobrecargado. En este caso, solo sirve para llamar al constructor de la clase klass
.
Ahora, sin más preámbulos aquí está el código para una versión sobrecargada de los operadores:
void* operator new(size_t size) {
// See Effective C++, Item 8 for an explanation.
if (size == 0)
size = 1;
cerr << "Allocating " << size << " bytes of memory:";
while (true) {
void* ret = custom_malloc(size);
if (ret != 0) {
cerr << " @ " << ret << endl;
return ret;
}
// Retrieve and call new handler, if available.
new_handler handler = set_new_handler(0);
set_new_handler(handler);
if (handler == 0)
throw bad_alloc();
else
(*handler)();
}
}
void operator delete(void* p) {
cerr << "Freeing pointer @ " << p << "." << endl;
custom_free(p);
}
Este código solo usa una implementación personalizada de malloc
/ free
internamente, como lo hacen la mayoría de las implementaciones. También crea una salida de depuración. Considera el siguiente código:
int main() {
int* pi = new int(42);
cout << *pi << endl;
delete pi;
}
Dio la siguiente salida:
Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.
Ahora, este código hace algo fundamentalmente diferente de la implementación estándar de la operator delete
del operator delete
: ¡no testeó los punteros nulos! El compilador no comprueba esto, por lo que el código anterior se compila, pero puede dar errores desagradables en el tiempo de ejecución cuando intenta eliminar los punteros nulos.
Sin embargo, como he dicho antes, este comportamiento es realmente inesperado y un escritor de la biblioteca debe tener cuidado de buscar punteros nulos en la operator delete
. Esta versión ha mejorado mucho:
void operator delete(void* p) {
if (p == 0) return;
cerr << "Freeing pointer @ " << p << "." << endl;
free(p);
}
En conclusión, aunque una implementación descuidada de la operator delete
del operator delete
puede requerir comprobaciones nulas explícitas en el código del cliente, este es un comportamiento no estándar y solo debe tolerarse en soporte heredado ( si es que lo es ).
Eliminar los controles para NULL internamente. Tu prueba es redundante
Eliminar null es un no-op. No hay ninguna razón para comprobar nulo antes de llamar a eliminar.
Es posible que desee comprobar null por otros motivos si el puntero es nulo contiene información adicional que le interesa.
En cuanto a mis observaciones, eliminar un puntero nulo utilizando delete es seguro en las máquinas basadas en Unix ike PARISC y itanium. Pero es bastante inseguro para los sistemas Linux ya que el proceso colapsaría en ese momento.
Es perfectamente "seguro" eliminar un puntero nulo; efectivamente equivale a no-op.
La razón por la que es posible que desee comprobar nulo antes de eliminar es que intentar eliminar un puntero nulo podría indicar un error en su programa.
No hay ninguna razón para verificar NULL antes de eliminar. La asignación de NULL después de la eliminación puede ser necesaria si en alguna parte de las comprobaciones de código se realiza si algún objeto ya está asignado mediante la realización de una verificación NULL. Un ejemplo sería algún tipo de datos en caché que se asigna a pedido. Siempre que elimine el objeto caché, asigna NULL al puntero para que el código que asigna el objeto sepa que necesita realizar una asignación.
Si pSomeObject es NULL, eliminar no hará nada. Entonces no, no tienes que verificar NULL.
Consideramos que es una buena práctica asignar NULL al puntero después de eliminarlo si es posible que algunos nudillos intenten utilizar el puntero. Usar un puntero NULL es ligeramente mejor que usar un puntero a quién sabe qué (el puntero NULL causará un bloqueo, el puntero a la memoria eliminada puede no serlo)