Pregunta

A menudo veo la comprobación de código heredado para 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?

¿Fue útil?

Solución

Es perfectamente "seguro" para eliminar un puntero nulo; efectivamente equivale a un no-op.

La razón por la que es posible que desee verificar si es nulo antes de eliminarlo es que intentar eliminar un puntero nulo podría indicar un error en su programa.

Otros consejos

El estándar C ++ garantiza que es legal usar un puntero nulo en una eliminación-expresión (§8.5.2.5 / 2). Sin embargo, es sin especificar si esto llamará a una función de desasignación ( operador eliminar o operador eliminar [] ; §8.5.2.5 / 7, nota ).

Si una función de desasignación predeterminada (es decir, proporcionada por la biblioteca estándar) se llama con un puntero nulo, 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, & nbsp; qué sucede cuando sobrecargamos operador eliminar (o operador eliminar [] ).

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, estableciendo el puntero en 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 programación defensiva : hará que el comportamiento del programa sea un poco más predecible en el caso de un error: acceder al puntero después de la eliminación dará como resultado un acceso de puntero nulo en lugar de un acceso a una ubicación de memoria aleatoria. Aunque ambas operaciones son un comportamiento indefinido, el comportamiento de un acceso de puntero nulo es mucho más predecible en la práctica (lo más frecuente es que se produzca un bloqueo directo en lugar de corrupción de memoria). Dado que los daños en la memoria son especialmente difíciles de depurar, restablecer los punteros eliminados ayuda a la depuración.

- Por supuesto, esto es tratar el síntoma en lugar de la causa (es decir, el error). Debe tratar el restablecimiento de los punteros como código de olor. El código limpio y moderno de C ++ hará que la propiedad de la memoria sea clara y esté estáticamente verificada (mediante el uso de punteros inteligentes o mecanismos equivalentes), y así evitará esta situación.

Bonus: una explicación de la sobrecarga del operador eliminar :

operator delete es (a pesar de su nombre) una función que puede sobrecargarse como cualquier otra función. Esta función se llama internamente para cada llamada de operator delete con argumentos coincidentes. Lo mismo es cierto para operator new .

Sobrecargar operator new (y luego también operator delete ) tiene sentido en algunas situaciones en las que 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 C ++ efectivo .

Por ahora, digamos que queremos sobrecargar la versión global de operator new para la depuración. Antes de hacer esto, un breve aviso sobre 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 a new con una sintaxis ligeramente extraña. Esta es una llamada a la llamada ubicación new que toma una dirección y construye un objeto en esa dirección. Este operador también se puede sobrecargar. 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, al igual que la mayoría de las implementaciones. También crea una salida de depuración. Considere el siguiente código:

int main() {
    int* pi = new int(42);
    cout << *pi << endl;
    delete pi;
}

Produjo el siguiente resultado:

Allocating 4 bytes of memory: @ 0x100160
42
Freeing pointer @ 0x100160.

Ahora, este código hace algo fundamentalmente diferente a la implementación estándar de operator delete : ¡No probó punteros nulos! El compilador no verifica esto. el código anterior se compila pero puede dar errores desagradables en tiempo de ejecución cuando intenta eliminar punteros nulos.

Sin embargo, como dije antes, este comportamiento es realmente inesperado y un escritor de la biblioteca debería asegurarse de comprobar los punteros nulos en el operador eliminar . 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 operator delete puede requerir verificaciones nulas explícitas en el código del cliente, este es un comportamiento no estándar y solo debe tolerarse en el soporte heredado ( si está en todos ).

Eliminar cheques para NULL internamente. Tu prueba es redundante

Eliminar null es un no-op. No hay ninguna razón para verificar si es nulo antes de llamar a eliminar.

Es posible que desee comprobar si es nulo por otros motivos si el puntero que es nulo contiene información adicional que le interesa.

Según C ++ 03 5.3.5 / 2, es seguro eliminar un puntero nulo. Lo siguiente se cita del estándar:

  

En cualquiera de las alternativas, si el valor del operando de delete es el   puntero nulo la operación no tiene efecto.

Si pSomeObject es NULL, eliminar no hará nada. Así que no, no tiene que verificar NULL.

Consideramos que es una buena práctica asignar NULL al puntero después de eliminarlo, si es posible que algún cabeza hueca pueda intentar usar el puntero. Usar un puntero NULL es un poco mejor que usar un puntero a quién sabe qué (el puntero NULL provocará un bloqueo, el puntero a la memoria eliminada puede no serlo)

No hay ninguna razón para verificar NULL antes de eliminar. Asignar NULL después de la eliminación podría ser necesario si en algún lugar del código se realizan verificaciones si algún objeto ya está asignado al realizar una verificación NULL. Un ejemplo sería algún tipo de datos en caché que se asignan bajo demanda. Cada vez que borra el objeto de caché, asigna NULL al puntero para que el código que asigna el objeto sepa que necesita realizar una asignación.

Creo que el desarrollador anterior lo codificó de forma redundante para ahorrar algunos milisegundos: Es bueno que el puntero se establezca en NULL al eliminarlo, por lo que puede usar una línea como la siguiente justo después de eliminar el objeto:

if(pSomeObject1!=NULL) pSomeObject1=NULL;

Pero luego borrar está haciendo esa comparación exacta de todos modos (no hacer nada si es NULL). ¿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 un poco redundante si ya tuviera ese valor.

Entonces, mi apuesta es que el autor de esas líneas trató de garantizar que pSomeObject1 siempre sea NULL después de ser eliminado, sin incurrir en el costo de una prueba y asignación potencialmente innecesarias.

Depende de lo que estés haciendo. Algunas implementaciones anteriores de free , por ejemplo, no serán felices 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 un   rutina Xlib de propósito general que   libera los datos especificados. Debes   úselo para liberar cualquier objeto que fuera   asignado por Xlib, a menos que sea una alternativa   la función se especifica explícitamente para   el objeto. Un puntero NULL no puede ser   pasó a esta función.

Por lo tanto, considere liberar los punteros NULL como un error y estará seguro.

En cuanto a mis observaciones, eliminar un puntero nulo usando delete es seguro en máquinas basadas en Unix, como ike PARISC e itanium. Pero es bastante inseguro para los sistemas Linux ya que el proceso colapsaría entonces.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top