¿Es seguro eliminar un puntero NULL?

299

¿Es seguro eliminar un puntero NULL?

¿Y es un buen estilo de codificación?

qiuxiafei
fuente
21
Una buena práctica es escribir programas C ++ sin una sola llamada a delete. Use RAII en su lugar. Es decir, el uso std::vector<T> v(100);en lugar de T* p = new T[100];, utilizar punteros inteligentes como unique_ptr<T>y shared_ptr<T>que se encargan de la eliminación en lugar de punteros primas etc.
fredoverflow
8
gracias a make_shared(c ++ 11) y make_unique(c ++ 14) el programa debe contener cero de newedelete
sp2danny
2
Todavía puede haber algunos casos raros que requieren nuevo / eliminar, por ejemplo, atomic <T *>: atomic <unique_ptr <T>> no está permitido y atomic <shared_ptr <T>> tiene una sobrecarga que puede ser inaceptable en algunos casos.
atb
2
Para declarar una clase con administración de recursos utilizando RAII, debe llamar a nuevo y eliminar ¿verdad ?, o está diciendo que hay alguna clase de plantilla para ocultar esto, incluso esto.
VinGarcia
2
@VinGarcia El punto es que la mayoría de los códigos de usuario / cliente (es decir, sin biblioteca) nunca deberían tener que escribir newo delete. Clases diseñadas para administrar los recursos, donde los componentes estándar no pueden hacer el trabajo, se pueden hacer, por supuesto, lo que tienen que hacer, pero el punto es que se hacen las cosas feas con la memoria que gestionan, no el código del usuario final. Entonces, cree su propia biblioteca / clase auxiliar para hacer new/ delete, y use esa clase en lugar de ellos.
underscore_d

Respuestas:

265

deleterealiza la verificación de todos modos, por lo que verificarlo en su lado agrega sobrecarga y se ve más feo. Una muy buena práctica es establecer el puntero en NULL después delete(ayuda a evitar la eliminación doble y otros problemas de corrupción de memoria similares).

También me encantaría si, deletepor defecto, estableciera el parámetro en NULL como en

#define my_delete(x) {delete x; x = NULL;}

(Sé acerca de los valores R y L, pero ¿no sería bueno?)

ruslik
fuente
72
Tenga en cuenta que todavía puede haber varios otros punteros apuntando al mismo objeto, incluso si establece uno en NULL al eliminarlo.
sth
13
En la mayoría de los casos en mi código, el puntero se sale del alcance una vez que se ha eliminado. Mucho más seguro que simplemente establecerlo en NULL.
enero
142
Una práctica muy común es no establecer el puntero en NULL después de eliminarlo. Establecer un puntero en NULL después de eliminarlo enmascara los errores de asignación de memoria, lo cual es algo muy malo. Un programa que es correcto no elimina un puntero dos veces, y un programa que sí elimina un puntero dos veces debería bloquearse.
Damon
15
@Alice: Es irrelevante lo que dice el estándar al respecto. El estándar definido eliminar un puntero nulo que era válido por alguna razón absurda hace 30 años, por lo que es legal (muy probablemente un legado en C). Pero eliminar el mismo puntero dos veces (incluso después de cambiar su patrón de bits) sigue siendo un error grave del programa. No por la redacción del estándar, sino por la lógica del programa y la propiedad. Como está eliminando un puntero nulo, ya que el puntero nulo no corresponde a ningún objeto , por lo que nada podría ser eliminado. Un programa debe saber exactamente si un objeto es válido y quién lo posee, y cuándo se puede eliminar.
Damon
27
@Damon Sin embargo, a pesar de estas abrogaciones de sus reglas de propiedad draconianas, las estructuras sin bloqueo son probablemente más robustas que las basadas en bloqueo. Y sí, mis compañeros de trabajo realmente me aman por el perfil de ejecución mejorado que proporcionan estas estructuras y la rigurosa seguridad de los hilos que mantienen, lo que permite razonar más fácilmente sobre el código (excelente para el mantenimiento). Sin embargo, nada de esto ni su ataque personal implícito tienen que ver con ninguna definición de corrección, validez o propiedad. Lo que propone es una buena regla general, pero no es una ley universal ni está consagrada en el estándar.
Alice
77

Del borrador de C ++ 0x Standard.

$ 5.3.5 / 2 - "[...] En cualquiera de las alternativas, el valor del operando de delete puede ser un valor de puntero nulo. [... '"

Por supuesto, nadie 'borraría' un puntero con valor NULL, pero es seguro hacerlo. Idealmente, uno no debería tener un código que borre un puntero NULL. Pero a veces es útil cuando la eliminación de punteros (por ejemplo, en un contenedor) ocurre en un bucle. Dado que la eliminación de un valor de puntero NULL es segura, realmente se puede escribir la lógica de eliminación sin verificaciones explícitas para eliminar el operando NULL.

Por otro lado, C Standard $ 7.20.3.2 también dice que 'gratis' en un puntero NULL no hace ninguna acción.

La función libre hace que el espacio al que apunta ptr se desasigne, es decir, esté disponible para una asignación adicional. Si ptr es un puntero nulo, no se produce ninguna acción.

Chubsdad
fuente
2
Realmente me gustaría esta respuesta para sus citas si no introdujera intencionalmente la ineficiencia en el código no optimizado. Como dice la respuesta aceptada, la eliminación de un puntero nulo es un no-op. Por lo tanto, verificar si un puntero es nulo antes de eliminarlo es completamente extraño.
codetaku
47

Si es seguro

No hay daño en eliminar un puntero nulo; a menudo reduce el número de pruebas en la cola de una función si los punteros no asignados se inicializan a cero y luego simplemente se eliminan.


Dado que la oración anterior ha causado confusión, un ejemplo, que no es una excepción segura, de lo que se describe:

void somefunc(void)
{
    SomeType *pst = 0;
    AnotherType *pat = 0;

    
    pst = new SomeType;
    
    if (…)
    {
        pat = new AnotherType[10];
        
    }
    if (…)
    {
        code using pat sometimes
    }

    delete[] pat;
    delete pst;
}

Hay todo tipo de liendres que se pueden elegir con el código de muestra, pero el concepto es (espero) claro. Las variables de puntero se inicializan a cero para que las deleteoperaciones al final de la función no necesiten probar si no son nulas en el código fuente; el código de la biblioteca realiza esa verificación de todos modos.

Jonathan Leffler
fuente
Tuve que leer eso varias veces para darle sentido. Debe significar inicializarlos a cero en la parte superior del método, o durante el mismo, no en la cola, ¿verdad? De lo contrario, simplemente eliminaría tanto la reducción a cero como la eliminación.
Marqués de Lorne
1
@EJP: Un esquema no del todo inverosímil de una función podría ser: void func(void) { X *x1 = 0; Y *y1 = 0; … x1 = new[10] X; … y1 = new[10] Y; … delete[] y1; delete[] x1; }. No he mostrado ninguna estructura de bloque o saltos, pero las delete[]operaciones al final son seguras debido a las inicializaciones al inicio. Si algo saltó al final después de que x1se asignó y antes y1se asignó y no hubo inicialización de y1, entonces habría un comportamiento indefinido, y si bien el código podría probar la nulidad (de x1y y1) antes de las eliminaciones, no hay necesidad de hacerlo entonces.
Jonathan Leffler
22

Eliminar un puntero nulo no tiene ningún efecto. No es un buen estilo de codificación necesariamente porque no es necesario, pero tampoco está mal.

Si está buscando buenas prácticas de codificación, considere usar punteros inteligentes en su lugar, por lo que no es necesario delete.

Brian R. Bondy
fuente
20
el momento en que las personas desean eliminar un puntero NULL es cuando no están seguros de si contiene NULL ... si supieran que es NULL, entonces no estarían considerando eliminar y, por lo tanto, preguntar ;-).
Tony Delroy el
@ Tony: Mi punto era solo que no tendrá ningún efecto, y la presencia de dicho código que elimina un puntero que a veces contiene NULL no es necesariamente malo.
Brian R. Bondy el
3
Las verificaciones redundantes de la OMI ciertamente son malas para el rendimiento, la legibilidad y la mantenibilidad.
Paul
@paulm OP ciertamente no está hablando de ese tipo de maldad, más del tipo de Falla Seg / UB.
Invierno
8

Para complementar la respuesta de ruslik , en C ++ 14 puede usar esta construcción:

delete std::exchange(heapObject, nullptr);
ashrasmun
fuente
3

Es seguro a menos que haya sobrecargado el operador de eliminación. si sobrecargó el operador de eliminación y no maneja la condición nula, no es seguro en absoluto.


fuente
¿Podría agregar alguna explicación para su respuesta?
Marcin Nabiałek
-2

He experimentado que es no seguro (VS2010) para borrar [] NULL (es decir, la sintaxis de array). No estoy seguro de si esto está de acuerdo con el estándar C ++.

Que es seguro para borrar NULL (sintaxis escalar).

feest
fuente
66
Esto es ilegal y no lo creo.
Konrad Rudolph
55
Debería poder eliminar cualquier puntero nulo. Entonces, si se está rompiendo para usted, entonces probablemente tenga un error en el código que lo muestra.
Mysticial
3
§5.3.2 En la segunda alternativa (matriz de eliminación), el valor del operando de eliminación puede ser un valor de puntero nulo o un valor de puntero que resultó de una nueva expresión de matriz anterior.
sp2danny
1
@Opux El comportamiento de VS2010 alegado en la respuesta. Como se explicó en otros comentarios, es seguro hacerlo delete[] NULL.
Konrad Rudolph
1
@Opux Por eso escribí "No lo creo" en lugar de "eso está mal". Pero todavía no lo hago, y sería una violación bastante escandalosa y estúpida de la norma. VC ++ en general es bastante bueno para seguir las restricciones del estándar, y los lugares donde las viola tienen sentido históricamente.
Konrad Rudolph el