Establecer variable en NULL después de liberar

156

En mi empresa 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, configurarlo NULLno tiene ningún significado. ¿O me estoy perdiendo algo?

Si no tiene sentido en tales casos, voy a hablar con el "equipo de calidad" para eliminar esta regla de codificación. Por favor aconséjame.

Alphaneo
fuente
3
siempre es útil poder verificar si ptr == NULLantes de hacer algo con él. Si no anula sus punteros gratuitos, obtendrá un ptr != NULLpuntero inutilizable.
Ki Jéy
Los punteros colgantes pueden conducir a vulnerabilidades explotables como Use-After-Free .
Константин Ван

Respuestas:

285

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, que le informa de inmediato cuál es el error.

Para las variables locales, puede ser un poco inútil si es "obvio" que ya no se accede al puntero 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 verdadero valor de puntero.

Martin v. Löwis
fuente
3
No entiendo por qué "inicializaría punteros a NULL antes de que se les asigne un verdadero valor de puntero".
Paul Biggar
26
@Paul: En el caso específico, la declaración podría leerse int *nPtr=NULL;. Ahora, estaría de acuerdo en que esto sería redundante, con un malloc siguiendo en la línea siguiente. Sin embargo, si hay código entre la declaración y la primera inicialización, alguien podría comenzar a usar la variable a pesar de que aún no tiene valor. Si nulo-inicializa, obtiene el segfault; sin, puede volver a leer o escribir memoria aleatoria. Del mismo modo, si la variable luego se inicializa solo condicionalmente, los accesos defectuosos posteriores deberían provocar bloqueos instantáneos si recordaba que la inicialización era nula.
Martin v. Löwis
1
Personalmente, creo que en cualquier base de código no trivial, obtener un error por anular la referencia nula es tan vago como obtener un error por anular la referencia de una dirección que no es de su propiedad. Yo personalmente nunca me molesto.
wilhelmtell
9
Wilhelm, el punto es que con una desreferencia de puntero nulo se obtiene un bloqueo determinado y la ubicación real del problema. Un mal acceso puede bloquearse o no y dañar datos o comportamientos de manera inesperada en lugares inesperados.
Amit Naidu
44
En realidad, inicializar el puntero a NULL tiene al menos un inconveniente significativo: puede evitar que el compilador le advierta sobre variables no inicializadas. A menos que la lógica de su código realmente maneje explícitamente ese valor para el puntero (es decir, si (nPtr == NULL) dosomething ...) es mejor dejarlo como está.
Eric
37

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

Supuestamente, se supone que establecer un puntero en NULLafter freepreviene el temido problema de "doble libre" cuando se pasa el mismo valor de puntero a freemás de una vez. Sin embargo, en realidad, en 9 de cada 10 casos, el verdadero problema de "doble libre" ocurre cuando diferentes se utilizan objetos de puntero que tienen el mismo valor de puntero como argumentos para free. No hace falta decir que establecer un puntero en NULLdespués no freelogra absolutamente nada para evitar el problema en tales casos.

Por supuesto, es posible encontrarse con un problema de "doble libre" 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 mero "doble libre" accidental. Una forma adecuada de abordar 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 freemás de una vez. En tales casos, configurar el puntero NULLy considerar el problema "solucionado" 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 NULLo no NULL, está perfectamente bien establecer el valor del puntero en NULLafter free. Pero como regla general de "buenas prácticas" (como en "siempre ponga el puntero en NULLdespués free") es, una vez más, una falsa bien conocida y bastante inútil, a menudo seguida por algunas por razones puramente religiosas, como el vudú.

Hormiga
fuente
1
Seguro. No recuerdo haber causado un doble libre que se solucionaría al establecer el puntero en NULL después de la liberación, pero he causado un montón de cosas que no lo harían.
LnxPrgr3
44
@AnT "dudoso" es un poco demasiado. Todo depende del caso de uso. Si el valor del puntero se usa alguna vez en un sentido verdadero / falso, no es solo una práctica válida, es una mejor práctica.
Codificador
1
@Coder Completamente equivocado. Si el valor del puntero se usa en un verdadero sentido falso para saber si apuntó o no a un objeto antes de la llamada a liberar, no solo no es la mejor práctica, está mal . Por ejemplo: foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;. En este caso, el establecimiento barde NULLdespués de la llamada a freecausará la función de pensar que no tenía un bar y devolver el valor incorrecto!
David Schwartz
No creo que el beneficio principal sea protegerse contra un doble libre, más bien es atrapar punteros colgantes antes y de manera más confiable. Por ejemplo, al liberar una estructura que contiene recursos, punteros a memoria asignada, identificadores de archivos, etc., cuando libero los punteros de memoria contenidos y cierro los archivos contenidos, NULO miembros respectivos. Luego, si se accede a uno de los recursos a través de un puntero colgante por error, el programa tiende a fallar allí, siempre. De lo contrario, sin NULLing, los datos liberados podrían no sobrescribirse aún y el error podría no ser fácilmente reproducible.
jimhark
1
Estoy de acuerdo en que un código bien estructurado no debería permitir el caso en el que se accede a un puntero después de ser liberado o el caso en que se libera dos veces. Pero en el mundo real, mi código será modificado y / o mantenido por alguien que probablemente no me conoce y no tiene el tiempo y / o las habilidades para hacer las cosas correctamente (porque la fecha límite siempre es ayer). Por lo tanto, tiendo a escribir funciones a prueba de balas que no bloquean el sistema incluso si se usan incorrectamente.
mfloris
35

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 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 aleatoria de la memoria.

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.

Mike McNertney
fuente
1
+1 Esto es realmente un muy buen punto. No es el razonamiento sobre "doble libre" (que es completamente falso), sino esto . No soy fanático de la anulación mecánica de punteros después free, pero esto realmente tiene sentido.
ANT
Si pudiera acceder a un puntero después de liberarlo a través de ese mismo puntero, es aún más probable que acceda a un puntero después de liberar el objeto al que apunta a través de otro puntero. Por lo tanto, esto no lo ayuda en absoluto: aún debe usar algún otro mecanismo para asegurarse de no acceder a un objeto a través de un puntero después de haberlo liberado a través de otro. También podría usar ese método para proteger en el mismo caso de puntero también.
David Schwartz
1
@DavidSchwartz: No estoy de acuerdo con tu comentario. Cuando tuve que escribir una pila para un ejercicio universitario hace unas semanas, tuve un problema, investigué unas horas. Accedí a alguna memoria ya liberada en algún momento (la libre era algunas líneas demasiado pronto). Y a veces conduce a un comportamiento muy extraño. Si hubiera establecido el puntero en NULL después de liberarlo, habría habido una segfault "simple" y habría ahorrado un par de horas de trabajo. ¡Entonces +1 por esta respuesta!
mozzbozz
2
@katze_sonne Incluso un reloj parado es correcto dos veces al día. Es mucho más probable que establecer punteros en NULL oculte errores al evitar que los accesos erróneos a objetos ya liberados se segfaulen en un código que verifica NULL y luego silenciosamente no puede verificar un objeto que debería haber verificado. (Tal vez establecer punteros en NULL después de liberar en compilaciones de depuración específicas podría ser útil, o establecerlos en un valor diferente a NULL que garantice la seguridad predeterminada podría tener sentido. Pero que esta tontería sucedió para ayudarlo una vez no es un argumento a su favor .)
David Schwartz
@DavidSchwartz Bueno, eso suena razonable ... ¡Gracias por tu comentario, lo consideraré en el futuro! :) +1
mozzbozz
20

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_tmp = (p); free(*(p_tmp)); *(p_tmp) = NULL; } while (0)
#endif

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

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

desgarrado
fuente
3
La versión macro tiene un error sutil si lo usa después de una instrucción if sin corchetes.
Mark Ransom
¿Qué pasa con el (nulo) 0? Este código hace: if (x) myfree (& x); de lo contrario do_foo (); se convierte en if (x) {free (* (& x)); * (& x) = nulo; } nulo 0; de lo contrario do_foo (); El otro es un error.
jmucchiello
Esa macro es un lugar perfecto para el operador de coma: libre ( (p)), * (p) = nulo. Por supuesto, el siguiente problema es que evalúa * (p) dos veces. Debería ser {void * _pp = (p); Libre P); * _pp = nulo; } ¿No es divertido el preprocesador?
jmucchiello
55
La macro no debe estar entre corchetes, debe estar en un do { } while(0)bloque para que if(x) myfree(x); else dostuff();no se rompa.
Chris Lutz
3
Como dijo Lutz, el macro cuerpo do {X} while (0)es IMO, la mejor manera de hacer un macro cuerpo que "se sienta y funcione" como una función. La mayoría de los compiladores optimizan el ciclo de todos modos.
Mike Clark el
7

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 de depurar.

Tadeusz A. Kadłubowski
fuente
55
El programa no siempre se bloquea con un segfault. Si la forma en que accede al puntero significa que se aplica un desplazamiento lo suficientemente grande antes de desreferenciarlo, entonces puede llegar a la memoria direccionable: ((MyHugeStruct *) 0) -> fieldNearTheEnd. Y eso es incluso antes de tratar con hardware que no se desconecta por defecto en el acceso 0. Sin embargo, es más probable que el programa se bloquee con un segfault.
Steve Jessop
7

Establecer el puntero en la freememoria 'd 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 las cosas salieron mal.

Puedo ver su argumento: dado que nPtrestá fuera de alcance justo después nPtr = NULL, no parece haber una razón para configurarlo NULL. Sin embargo, en el caso de unstruct miembro o en otro lugar donde el puntero no está inmediatamente fuera de alcance, 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 aplicar automáticamente la regla, y mucho menos para que los desarrolladores la sigan. No está de más establecer punteros NULLdespués de cada libre, pero tiene el potencial de señalar grandes problemas.

Jared Oberhaus
fuente
7

El error más común en c es el doble gratis. 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, falla por defecto. Por lo tanto, la buena práctica es configurarlo NULL, para que pueda hacer una prueba y verificar si realmente necesita liberar esta memoria

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

También hay que tener en cuenta que free(NULL)no hará nada para que no tenga que escribir la declaración if. Realmente no soy 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.

RageZ
fuente
11
En realidad, solo puede llamar a free () sin marcar: free (NULL) se define como no hacer nada.
Ámbar
55
¿Eso no esconde errores? (Como liberar demasiado.)
Georg Schölly
1
Gracias, lo tengo. Lo intenté:p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang
55
Como dije, free(void *ptr) no se puede cambiar el valor del puntero que se pasa. Puede cambiar el contenido del puntero, los datos almacenados en esa dirección , pero no la dirección en sí o el valor del puntero . Eso requeriría free(void **ptr)(que aparentemente no está permitido por el estándar) o una macro (que está permitida y es perfectamente portátil, pero a la gente no le gustan las macros). Además, C no se trata de conveniencia, se trata de dar a los programadores tanto control como quieran. Si no quieren la sobrecarga adicional de establecer punteros NULL, no se les debe forzar.
Chris Lutz
2
Hay pocas cosas en el mundo que revelen la falta de profesionalismo por parte del autor del código C. Pero incluyen "verificar el puntero para NULL antes de llamar free" (junto con cosas tales como "emitir el resultado de funciones de asignación de memoria" o "uso irreflexivo de nombres de tipo con sizeof").
ANT
6

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

Trigo Mitch
fuente
4

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 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.

Steven Canfield
fuente
En Windows, al menos, las compilaciones de depuración establecerán la memoria en 0xdddddddd, de modo que cuando use un puntero para borrar la memoria que conoce de inmediato. Debería haber mecanismos similares en todas las plataformas.
i_am_jorf
2
jeffamaphone, el bloque de memoria eliminado podría haberse reasignado y asignado a otro objeto para cuando vuelva a usar el puntero.
Constantin
4

Del estándar ANSI C:

void free(void *ptr);

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. De lo contrario, si el argumento no coincide con un puntero devuelto anteriormente por la función calloc, malloc o realloc, o si el espacio ha sido desasignado por una llamada a free o realloc, el comportamiento es indefinido.

"El comportamiento indefinido" es casi siempre 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;
}
Vijay Mathew
fuente
@DrPizza: un error (en mi opinión) es algo que hace que su programa no funcione como debería. Si un doble libre oculto rompe su programa, es un error. Si funciona exactamente como se pretendía, no es un error.
Chris Lutz
@DrPizza: Acabo de encontrar un argumento por el cual uno debería configurarlo NULLpara evitar errores de enmascaramiento. stackoverflow.com/questions/1025589/… Parece que en cualquier caso se ocultan algunos errores.
Georg Schölly
1
Tenga en cuenta que un puntero a puntero vacío tiene sus problemas: c-faq.com/ptrs/genericpp.html
Seguro
3
@ Chris, no, el mejor enfoque es la estructura del código. No arrojes mallocs aleatorios y liberes por toda tu base de código, mantén las cosas relacionadas juntas. El "módulo" que asigna un recurso (memoria, archivo, ...) es responsable de liberarlo y tiene que proporcionar una función para hacerlo que también tenga cuidado con los punteros. Para cualquier recurso específico, tiene exactamente un lugar donde se asigna y un lugar donde se libera, ambos muy juntos.
Seguro
44
@ Chris Lutz: Hogwash. Si escribe código que libera el mismo puntero dos veces, su programa tiene un error lógico. Enmascarar ese error lógico haciendo que no se bloquee no significa que el programa sea correcto: todavía está haciendo algo sin sentido. No hay escenario en el que se justifique escribir un doble libre.
DrPizza
4

Creo 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, ya que 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 desea de la clase es que ese miembro señalará algo válido en cualquier momento, por lo que debe establecerse en nulo por esta razón. Una pequeña distinción pero creo importante.

En c ++ es importante estar siempre pensando quién es el dueño estos datos cuando asigna algo de memoria (a menos que esté utilizando punteros inteligentes, pero aun así es necesario pensarlo). Y este proceso tiende a conducir a que los punteros generalmente sean 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 en cualquier puntero a los datos que su diseño dice que posee la clase . Claramente, en este caso, debe establecer el puntero en NULL cuando elimina algo para indicar que no posee ningún dato antes.

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.

jcoder
fuente
4

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;
Jalindar
fuente
3

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 fuera más larga, 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 aconsejo 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.

i_am_jorf
fuente
2

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 como:


if(ptr)
   ptr->CallSomeMethod();

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

Aamir
fuente
55
En muchos casos, donde un puntero NULL no tiene sentido, sería preferible escribir una afirmación en su lugar.
Erich Kitzmueller
2

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();
}

ptermina en el mismo lugar en la pila que el primero nPtr, por lo que aún podría contener un puntero aparentemente válido. Asignar a *ppodrí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 ...

algo
fuente
2

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

pierrotlefou
fuente
2

La configuración de un puntero a NULL es 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.

Double-free conduce a un comportamiento indefinido, por lo general, daña la corrupción 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.

Por lo tanto, 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 requiera un puntero NULL y un comportamiento indefinido Es evadido.

diente filoso
fuente
2

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 de manera misteriosa.

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.

Jaap Weel
fuente
2

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 sea así, -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 incorrectamente. 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 la 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

Bill IV
fuente
Buena respuesta. Me gustaría agregar una cosa. Revisar la base de datos de errores es bueno por varias razones. Pero en el contexto de la pregunta original, tenga en cuenta que sería difícil saber cuántos problemas de puntero inválidos se evitaron, o al menos se detectaron tan pronto como para no haber llegado a la base de datos de errores. El historial de errores proporciona una mejor evidencia para agregar reglas de codificación.
jimhark
2

Hay dos razones:

Evite bloqueos cuando se libera doblemente

Escrito por RageZ en una pregunta duplicada .

El error más común en c es el doble gratis. 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, falla por defecto. Por lo tanto, la buena práctica es configurarlo NULL, para que pueda hacer una prueba y verificar si realmente necesita liberar esta memoria

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

También hay que tener en cuenta que free(NULL) no hará nada para que no tenga que escribir la declaración if. Realmente no soy 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 también de no tener que dejar al desarrollador la gestión de la memoria en su conjunto.

Evite usar punteros ya liberados

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

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, que le informa de inmediato cuál es el error.

Para las variables locales, puede ser un poco inútil si es "obvio" que ya no se accede al puntero 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 verdadero valor de puntero.

Georg Schölly
fuente
1

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 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

Hay formas de suprimir selectivamente los mensajes, de modo que aún pueda cumplir con ambos requisitos de control de calidad, si su equipo así lo decide.

Jens
fuente
1

Siempre es aconsejable 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 . p.ej:

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 puntero colgante . 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 una nueva variable de puntero. Si en el caso, usa el mismo puntero ( ptr ) y actualiza el valor en la dirección apuntada por el mismo puntero ( ptr ), entonces el programa terminará escribiendo un valor en el lugar donde apunta q (ya que p y q son apuntando a la misma dirección (0x1000 )).

p.ej

*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.
Pankaj Kumar Thapa
fuente
1

Larga historia corta: 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.

Ehsan
fuente