Pregunta

En mi compañía hay una regla de codificación que dice, después de liberar cualquier memoria, restablecer la variable a NULL. Por ejemplo ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

Siento que, en casos como el código que se muestra arriba, establecer NULL no tiene ningún significado. ¿O me estoy perdiendo algo?

Si no tiene sentido en tales casos, lo abordaré con el "equipo de calidad". para eliminar esta regla de codificación. Por favor consejo.

¿Fue útil?

Solución

Establecer punteros no utilizados en NULL es un estilo defensivo, que protege contra errores de puntero colgantes. Si se accede a un puntero colgante después de liberarlo, puede leer o sobrescribir la memoria aleatoria. Si se accede a un puntero nulo, se produce un bloqueo inmediato en la mayoría de los sistemas, indicándole de inmediato cuál es el error.

Para las variables locales, puede ser un poco inútil si es "obvio". que el puntero ya no se accede después de ser liberado, por lo que este estilo es más apropiado para los datos de los miembros y las variables globales. Incluso para variables locales, puede ser un buen enfoque si la función continúa después de liberar la memoria.

Para completar el estilo, también debe inicializar los punteros a NULL antes de que se les asigne un valor de puntero verdadero.

Otros consejos

Establecer un puntero en NULL después de free es una práctica dudosa que a menudo se populariza como una "buena programación". dictaminar sobre una premisa evidentemente falsa. Es una de esas verdades falsas que pertenecen a "suena bien". categoría, pero en realidad no logran absolutamente nada útil (y a veces conduce a consecuencias negativas).

Supuestamente, establecer un puntero a NULL después de free se supone que evita el temido " double free " problema cuando se pasa el mismo valor de puntero a free más de una vez. Sin embargo, en realidad, en 9 casos de cada 10, el real "doble gratis" El problema ocurre cuando los objetos de puntero diferentes que contienen el mismo valor de puntero se usan como argumentos para libre . No es necesario decir que establecer un puntero en NULL después de free no logra absolutamente nada para evitar el problema en tales casos.

Por supuesto, es posible encontrarse con "doble libre" problema cuando se utiliza el mismo objeto puntero como argumento para free . Sin embargo, en situaciones como esa normalmente se indica un problema con la estructura lógica general del código, no un simple "doble libre" accidental. Una forma adecuada de resolver el problema en tales casos es revisar y repensar la estructura del código para evitar la situación en la que se pasa el mismo puntero a free más de una vez. En tales casos, establecer el puntero en NULL y considerar el problema "arreglado" no es más que un intento de barrer el problema debajo de la alfombra. Simplemente no funcionará en el caso general, porque el problema con la estructura del código siempre encontrará otra forma de manifestarse.

Finalmente, si su código está específicamente diseñado para confiar en que el valor del puntero sea NULL o no NULL , está perfectamente bien establecer el valor del puntero en NULL después de free . Pero como una "buena práctica" general La regla (como en " establece siempre el puntero en NULL después de free ") es, una vez más, una falsificación conocida y bastante inútil, a menudo seguida por algunos por razones puramente religiosas, como el vudú.

La mayoría de las respuestas se han centrado en evitar un doble libre, pero establecer el puntero en NULL tiene otro beneficio. Una vez que libera un puntero, esa memoria está disponible para ser reasignada por otra llamada a malloc. Si todavía tiene el puntero original a su alrededor, puede terminar con un error en el que intenta usar el puntero después de liberar y corromper alguna otra variable, y luego su programa entra en un estado desconocido y pueden ocurrir todo tipo de cosas malas (se bloquea si tienes suerte, corrupción de datos si no tienes suerte). Si había establecido el puntero en NULL después de liberarlo, cualquier intento de leer / escribir a través de ese puntero más tarde daría como resultado una falla predeterminada, que generalmente es preferible a la corrupción de memoria aleatoria.

Por ambas razones, puede ser una buena idea establecer el puntero en NULL después de free (). Sin embargo, no siempre es necesario. Por ejemplo, si la variable de puntero se sale del alcance inmediatamente después de free (), no hay muchas razones para establecerla en NULL.

Esto se considera una buena práctica para evitar sobrescribir la memoria. En la función anterior, es innecesario, pero a menudo, cuando se hace, puede encontrar errores de aplicación.

Pruebe algo como esto en su lugar:

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

El DEBUG_VERSION le permite liberar el perfil en el código de depuración, pero ambos son funcionalmente iguales.

Edit : Agregado do ... mientras que como se sugiere a continuación, gracias.

Si llega al puntero que ha estado libre () d, podría romperse o no. Esa memoria puede reasignarse a otra parte de su programa y luego se daña la memoria,

Si configura el puntero en NULL, entonces si accede a él, el programa siempre se bloquea con un segfault. No más, a veces funciona '', no más, se bloquea de forma impredecible ''. Es mucho más fácil depurar.

Establecer el puntero en la memoria free significa que cualquier intento de acceder a esa memoria a través del puntero se bloqueará inmediatamente, en lugar de causar un comportamiento indefinido. Hace que sea mucho más fácil determinar dónde salieron las cosas.

Puedo ver su argumento: dado que nPtr está fuera de alcance justo después de nPtr = NULL , no parece haber una razón para establecerlo en < code> NULL . Sin embargo, en el caso de un miembro struct o en otro lugar donde el puntero no esté fuera de alcance inmediatamente, tiene más sentido. No es evidente de inmediato si ese puntero será utilizado nuevamente por código que no debería estar usándolo.

Es probable que la regla se establezca sin hacer una distinción entre estos dos casos, porque es mucho más difícil hacer cumplir la regla automáticamente, y mucho menos para que los desarrolladores la sigan. No está de más establecer punteros en NULL después de cada libre, pero tiene el potencial de señalar grandes problemas.

el error más común en c es el doble libre. Básicamente haces algo así

free(foobar);
/* lot of code */
free(foobar);

y termina siendo bastante malo, el sistema operativo intenta liberar algo de memoria ya liberada y, por lo general, está predeterminada. Por lo tanto, la buena práctica es establecer NULL , para que pueda hacer una prueba y verificar si realmente necesita liberar esta memoria

if(foobar != null){
  free(foobar);
}

también debe tenerse en cuenta que free (NULL) no hará nada, por lo que no tiene que escribir la declaración if. No soy realmente un gurú del sistema operativo, pero soy bastante bueno, incluso ahora, la mayoría de los sistemas operativos se bloquearían en doble libre.

Esa es también una razón principal por la que todos los idiomas con recolección de basura (Java, dotnet) estaban tan orgullosos de no tener este problema y de no tener que dejar a los desarrolladores la administración de la memoria como un todo.

La idea detrás de esto es detener la reutilización accidental del puntero liberado.

Esto (puede) ser realmente importante. Aunque libera la memoria, una parte posterior del programa podría asignar algo nuevo que aterrice en el espacio. Su viejo puntero ahora apuntaría a una porción válida de memoria. Entonces es posible que alguien use el puntero, lo que da como resultado un estado de programa no válido.

Si anula NULL el puntero, cualquier intento de usarlo eliminará la referencia 0x0 y se bloqueará allí, lo que es fácil de depurar. Los punteros aleatorios que apuntan a memoria aleatoria son difíciles de depurar. Obviamente no es necesario, pero es por eso que está en un documento de mejores prácticas.

Del estándar ANSI C:

void free(void *ptr);
  

La función libre causa el espacio   señalado por ptr para ser desasignado,   es decir, disponible para más   asignación. Si ptr es un puntero nulo,   No se produce ninguna acción. De lo contrario, si el   argumento no coincide con un puntero   antes devuelto por el calloc,   malloc, o función realloc, o si   el espacio ha sido desasignado por un   llamar a libre o realloc, el comportamiento   no está definido.

" el comportamiento indefinido " Casi siempre es un bloqueo del programa. Para evitar esto, es seguro restablecer el puntero a NULL. free () en sí mismo no puede hacer esto ya que se pasa solo un puntero, no un puntero a un puntero. También puede escribir una versión más segura de free () que anule el puntero:

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

Considero que esto es de poca ayuda, ya que, según mi experiencia, cuando las personas acceden a una asignación de memoria liberada, casi siempre es porque tienen otro puntero en alguna parte. Y luego entra en conflicto con otro estándar de codificación personal que es "Evitar el desorden inútil", por lo que no lo hago porque creo que rara vez ayuda y hace que el código sea un poco menos legible.

Sin embargo, no estableceré la variable en nulo si se supone que el puntero no se usará nuevamente, pero a menudo el diseño de nivel superior me da una razón para establecerlo en nulo de todos modos. Por ejemplo, si el puntero es miembro de una clase y he eliminado lo que señala, entonces el " contrato " si lo que le gusta de la clase es que ese miembro señalará algo válido en cualquier momento, por lo que debe establecerse en nulo por ese motivo. Una pequeña distinción pero creo importante.

En c ++ es importante estar siempre pensando quién es el propietario de estos datos cuando asigna algo de memoria (a menos que esté utilizando punteros inteligentes, pero aun así es necesario reflexionar). Y este proceso tiende a conducir a que los punteros sean generalmente miembros de alguna clase y, en general, desea que una clase esté en un estado válido en todo momento, y la forma más fácil de hacerlo es establecer la variable miembro en NULL para indicar que apunta a nada ahora.

Un patrón común es establecer todos los punteros miembros en NULL en el constructor y hacer que la llamada destructor elimine cualquier puntero a los datos que su diseño dice que la clase posee . Claramente, en este caso, debe establecer el puntero en NULL cuando elimina algo para indicar que no posee ningún dato antes.

Entonces, para resumir, sí, a menudo configuro el puntero en NULL después de eliminar algo, pero es como parte de un diseño más amplio y reflexiona sobre quién posee los datos en lugar de seguir ciegamente una regla estándar de codificación. No lo haría en su ejemplo, ya que creo que no hay ningún beneficio en hacerlo y agrega "desorden". que en mi experiencia es tan responsable de los errores y el mal código como este tipo de cosas.

Recientemente me encontré con la misma pregunta después de buscar la respuesta. Llegué a esta conclusión:

Es la mejor práctica, y uno debe seguir esto para que sea portátil en todos los sistemas (integrados).

free () es una función de biblioteca, que varía a medida que uno cambia la plataforma, por lo que no debe esperar que después de pasar el puntero a esta función y después de liberar memoria, este puntero se establezca en NULL . Este puede no ser el caso de alguna biblioteca implementada para la plataforma.

así que siempre ve por

free(ptr);
ptr = NULL;

Esta regla es útil cuando intenta evitar los siguientes escenarios:

1) Tiene una función realmente larga con lógica complicada y administración de memoria y no desea reutilizar accidentalmente el puntero para borrar la memoria más adelante en la función.

2) El puntero es una variable miembro de una clase que tiene un comportamiento bastante complejo y no desea reutilizar accidentalmente el puntero para borrar la memoria en otras funciones.

En su escenario, no tiene mucho sentido, pero si la función se alargara, podría ser importante.

Puede argumentar que establecerlo en NULL puede enmascarar errores lógicos más adelante, o en el caso de que asuma que es válido, aún se bloquea en NULL, por lo que no importa.

En general, le aconsejaría que lo configure en NULL cuando considere que es una buena idea, y que no se moleste cuando piense que no vale la pena. En su lugar, concéntrese en escribir funciones cortas y clases bien diseñadas.

Para agregar a lo que otros han dicho, un buen método de uso del puntero es verificar siempre si es un puntero válido o no. Algo así como:


if(ptr)
   ptr->CallSomeMethod();

Marcar explícitamente el puntero como NULL después de liberarlo permite este tipo de uso en C / C ++.

Esto podría ser más un argumento para inicializar todos los punteros a NULL, pero algo como esto puede ser un error muy furtivo:

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

p termina en el mismo lugar en la pila que el anterior nPtr , por lo que aún podría contener un puntero aparentemente válido. Asignar a * p podría sobrescribir todo tipo de cosas no relacionadas y provocar errores feos. Especialmente si el compilador inicializa las variables locales con cero en modo de depuración pero no una vez que se activan las optimizaciones. Por lo tanto, las compilaciones de depuración no muestran signos del error mientras que las compilaciones de lanzamiento explotan al azar ...

Establecer el puntero que se acaba de liberar a NULL no es obligatorio, pero es una buena práctica. De esta manera, puede evitar 1) usar un apuntado liberado 2) liberarlo dos veces

Configura un puntero a NULL para proteger contra el llamado doble-libre, una situación en la que free () se llama más de una vez para la misma dirección sin reasignar el bloque en esa dirección.

Doble libre conduce a un comportamiento indefinido, por lo general, daña la pila o bloquea el programa de inmediato. Llamar a free () para un puntero NULL no hace nada y, por lo tanto, se garantiza que sea seguro.

Entonces, la mejor práctica a menos que ahora esté seguro de que el puntero deja el alcance inmediatamente o muy pronto después de free () es establecer ese puntero en NULL para que, incluso si se vuelve a llamar a free (), ahora se llame a un puntero NULL y el comportamiento indefinido se evade.

La idea es que si intenta desreferenciar el puntero que ya no es válido después de liberarlo, desea fallar con fuerza (por defecto) en lugar de en silencio y misteriosamente.

Pero ... ten cuidado. No todos los sistemas causan una falla predeterminada si desreferencia NULL. En (al menos algunas versiones de) AIX, * (int *) 0 == 0, y Solaris tiene compatibilidad opcional con esta " característica de AIX. & Quot;

A la pregunta original: Establecer el puntero en NULL directamente después de liberar el contenido es una pérdida de tiempo completa, siempre que el código cumpla con todos los requisitos, se depure completamente y nunca se vuelva a modificar. Por otro lado, la anulación defensiva de un puntero que se ha liberado puede ser bastante útil cuando alguien agrega un nuevo bloque de código debajo de free (), cuando el diseño del módulo original no es correcto y, en el caso de que lo haga, -compila-pero-no-hace-lo-que-quiero errores.

En cualquier sistema, existe el objetivo inalcanzable de facilitar la tarea correcta y el costo irreducible de las mediciones inexactas. En C se nos ofrece un conjunto de herramientas muy afiladas y muy fuertes, que pueden crear muchas cosas en manos de un trabajador calificado e infligir todo tipo de lesiones metafóricas cuando se manejan de manera incorrecta. Algunos son difíciles de entender o usar correctamente. Y las personas, siendo naturalmente reacias al riesgo, hacen cosas irracionales como verificar un puntero por valor NULO antes de llamar gratis con él ...

El problema de medición es que cada vez que intentas dividir lo bueno de lo menos bueno, cuanto más complejo es el caso, más probable es que obtengas una medición ambigua. Si el objetivo es mantener solo buenas prácticas, entonces algunas ambiguas se descartan con las que en realidad no son buenas. SI su objetivo es eliminar lo que no es bueno, entonces las ambigüedades pueden quedarse con lo bueno. Los dos objetivos, mantener solo el bien o eliminar claramente el mal, parecen ser diametralmente opuestos, pero generalmente hay un tercer grupo que no es ni uno ni el otro, algunos de los dos.

Antes de presentar un caso con el departamento de calidad, intente revisar la base de datos de errores para ver con qué frecuencia, si alguna vez, los valores de puntero no válidos causaron problemas que tuvieron que escribirse. Si desea hacer una diferencia real, identifique el problema más común en su código de producción y proponga tres formas de evitarlo

Hay dos razones:

Evita los bloqueos al doble liberación

Escrito por RageZ en un pregunta duplicada .

  

El error más común en c es el doble   gratis. Básicamente haces algo como   que

free(foobar);
/* lot of code */
free(foobar);
     

y termina bastante mal, el sistema operativo intenta   para liberar algo de memoria ya liberada y   en general es por defecto. Entonces lo bueno   la práctica es establecer NULL , para que   puede hacer una prueba y verificar si realmente   necesita liberar esta memoria

if(foobar != NULL){
  free(foobar);
}
     

también se debe tener en cuenta que free (NULL)   no hará nada para que no tengas que   escribe la declaración if. yo no soy   Realmente un gurú del sistema operativo, pero soy bastante parejo   ahora la mayoría de los sistemas operativos se estrellarían   gratis.

     

Esa es también una razón principal por la cual todos   idiomas con recolección de basura   (Java, dotnet) estaba tan orgulloso de no   teniendo este problema y tampoco   tener que dejar al desarrollador el   gestión de memoria en su conjunto.

Evite utilizar punteros ya liberados

Escrito por Martin v. Löwis en un otra respuesta .

  

Establecer punteros no utilizados en NULL es un   estilo defensivo, protegiendo contra   colgando errores puntero. Si un colgando   se accede al puntero después de liberarlo,   puedes leer o sobrescribir al azar   memoria. Si se accede a un puntero nulo,   obtienes un choque inmediato en la mayoría   sistemas, diciéndole de inmediato qué   el error es.

     

Para variables locales, puede ser un   un poco inútil si es   "obvio" que el puntero no es   accedió más después de ser liberado, así que   este estilo es más apropiado para   datos de miembros y variables globales. Incluso   para variables locales, puede ser un buen   enfoque si la función continúa   después de que se libere la memoria.

     

Para completar el estilo, también debe   inicializar punteros a NULL antes   se les asigna un puntero verdadero   valor.

Como tiene un equipo de garantía de calidad, permítame agregar un punto menor sobre el control de calidad. Algunas herramientas de control de calidad automatizadas para C marcarán las asignaciones a los punteros liberados como " asignación inútil para ptr " ;. Por ejemplo, PC-lint / FlexeLint de Gimpel Software dice tst.c 8 Advertencia 438: Último valor asignado a la variable 'nPtr' (definido en la línea 5) no utilizado

Hay formas de suprimir selectivamente los mensajes, para que pueda cumplir con ambos requisitos de control de calidad, en caso de que su equipo así lo decida.

Siempre es recomendable declarar una variable de puntero con NULL como,

int *ptr = NULL;

Digamos que ptr apunta a la dirección de memoria 0x1000 . Después de usar free (ptr) , siempre es recomendable anular la variable de puntero declarando nuevamente a NULL . por ejemplo:

free(ptr);
ptr = NULL;

Si no se vuelve a declarar a NULL , la variable de puntero sigue apuntando a la misma dirección ( 0x1000 ), esta variable de puntero se llama colgar puntero . Si define otra variable de puntero (digamos, q ) y asigna dinámicamente la dirección al nuevo puntero, existe la posibilidad de tomar la misma dirección ( 0x1000 ) por nuevo puntero variable. Si en este caso, utiliza el mismo puntero ( ptr ) y actualiza el valor en la dirección señalada por el mismo puntero ( ptr ), el programa terminará escribiendo un valor al lugar donde apunta q (ya que p y q apuntan a la misma dirección ( 0x1000 ) ).

por ejemplo

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.

Breve historia: no desea acceder accidentalmente (por error) a la dirección que ha liberado. Porque, cuando libera la dirección, permite que esa dirección en el montón se asigne a alguna otra aplicación.

Sin embargo, si no establece el puntero en NULL y, por error, intente desreferenciar el puntero o cambiar el valor de esa dirección; TODAVÍA PUEDES HACERLO. PERO NO ALGO QUE QUIERAS HACER LÓGICAMENTE.

¿Por qué todavía puedo acceder a la ubicación de memoria que he liberado? Porque: es posible que haya liberado la memoria, pero la variable de puntero todavía tenía información sobre la dirección de memoria de almacenamiento dinámico. Entonces, como estrategia defensiva, configúrelo como NULL.

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