validar - Estableciendo la variable a NULL después de libre
si existe variable en php (23)
A la pregunta original: Establecer el puntero a NULL directamente después de liberar el contenido es una completa pérdida de tiempo, siempre que el código cumpla con todos los requisitos, esté completamente depurado y nunca más se vuelva a modificar. Por otro lado, NULL de manera defensiva un puntero que se ha liberado puede ser bastante útil cuando alguien agrega un nuevo bloque de código por debajo del free (), cuando el diseño del módulo original no es correcto, y en el caso de este -compila-pero-no-hace-qué-yo-quiero errores.
En cualquier sistema, hay un objetivo inalcanzable de hacer que lo más fácil sea lo correcto y el costo irreductible 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 si se manipulan de forma incorrecta. Algunos son difíciles de entender o usar correctamente. Y las personas, por ser reacias al riesgo natural, hacen cosas irracionales como comprobar el valor NULL de un puntero antes de llamar gratis con él ...
El problema de la medición es que cada vez que intenta dividir el bien del menos bueno, mientras más complejo sea el caso, es más probable que obtenga una medición ambigua. Si el objetivo es mantener solo buenas prácticas, entonces algunas ambiguas se descartan con las realmente no buenas. SI tu objetivo es eliminar lo no bueno, entonces las ambigüedades pueden permanecer con lo bueno. Los dos objetivos, mantener solo lo bueno o eliminar claramente lo malo, parecerían 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 buscar en la base de datos de errores para ver con qué frecuencia, si alguna vez, los valores del puntero no válidos causaron problemas que debieron anotarse. Si desea marcar la diferencia, identifique el problema más común en su código de producción y proponga tres formas de prevenirlo
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 en NULL no tiene ningún significado. ¿O me estoy perdiendo algo?
Si no hay ningún significado en tales casos, voy a abordarlo con el "equipo de calidad" para eliminar esta regla de codificación. Por favor aconséjame.
Como tiene un equipo de garantía de calidad en funcionamiento, permítame agregar un punto menor sobre la garantía de calidad. Algunas herramientas automatizadas de control de calidad para C marcarán las asignaciones a los punteros liberados como "asignación inútil a 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, por lo que aún puede satisfacer ambos requisitos de QA, en caso de que su equipo así lo decida.
Configurar el puntero a 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á saliendo del alcance inmediatamente después de nPtr = NULL
, no parece haber una razón para establecerlo en NULL
. Sin embargo, en el caso de un miembro de struct
o en otro lugar donde el puntero no está saliendo inmediatamente del alcance, tiene más sentido. No es inmediatamente aparente si el puntero será utilizado nuevamente por un código que no debería usarlo.
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 automáticamente la regla, y mucho menos que los desarrolladores la sigan. No hace daño configurar punteros a NULL
después de cada NULL
gratuito, pero tiene el potencial de señalar grandes problemas.
Del estándar ANSI C:
void free(void *ptr);
La función gratuita hace que el espacio apuntado por ptr sea desasignado, es decir, que esté disponible para una asignación posterior. 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 no está definido.
"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 solo se pasa un puntero, no un puntero a un puntero. También puede escribir una versión más segura de free () que NULL el puntero:
void safe_free(void** ptr)
{
free(*ptr);
*ptr = NULL;
}
Encuentro que esto es de poca ayuda ya que, en mi experiencia, cuando las personas acceden a una asignación de memoria liberada es casi siempre 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", así que no lo hago porque creo que raramente ayuda y hace que el código sea menos legible.
Sin embargo, no estableceré la variable en nulo si no se supone que el puntero se use nuevamente, pero a menudo el diseño de nivel superior me da una razón para establecerlo como 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 apuntará a algo válido en cualquier momento, por lo que debe establecerse como nulo. por esta razón. Una pequeña distinción, pero creo que es importante.
En c ++, es importante estar siempre pensando a quién pertenecen estos datos cuando asigna algo de memoria (a menos que esté utilizando punteros inteligentes, pero incluso entonces se requiere algo de reflexión). Y este proceso tiende a hacer 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 manera 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 de miembros en NULL en el constructor y hacer que la llamada al destructor elimine en cualquier puntero a los datos que su diseño dice que pertenecen a la clase. Claramente, en este caso, debe establecer el puntero a NULL cuando borre algo para indicar que no posee ningún dato antes.
Entonces, para resumir, sí, a menudo configuro el puntero a NULL después de eliminar algo, pero es parte de un diseño más grande y piensa en 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 al hacerlo y agrega "desorden" que, en mi experiencia, es tan responsable de los errores y el código incorrecto como este tipo de cosas.
Esta regla es útil cuando intenta evitar los siguientes escenarios:
1) Tiene una función realmente larga con una administración de memoria y lógica complicada y no desea volver a utilizar accidentalmente el puntero a la memoria eliminada 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 a la memoria eliminada en otras funciones.
En su escenario, no tiene mucho sentido, pero si la función fuera más larga, podría importar.
Puede argumentar que establecerlo en NULL en realidad puede enmascarar errores lógicos más adelante, o en el caso en el que suponga que es válido, aún se bloquea en NULL, por lo que no importa.
En general, te aconsejaría que lo establezcas en NULL cuando creas que es una buena idea, y no te molestes cuando creas que no vale la pena. Céntrate en cambio en escribir funciones cortas y clases bien diseñadas.
Establecer el puntero que acaba de liberarse a NULL no es obligatorio, pero es una buena práctica. De esta forma, puedes evitar 1) usar un punto 2 liberado 2) liberarlo
Establecer punteros no utilizados en NULL es un estilo defensivo que protege contra errores de puntero colgando. 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 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 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 libera 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.
Establecer un puntero a NULL
después de free
es una práctica dudosa que a menudo se populariza como una regla de "buena programación" en una premisa patentemente falsa. Es una de esas verdades falsas que pertenecen a la categoría "suena bien" pero en realidad no logra absolutamente nada útil (y algunas veces conduce a consecuencias negativas).
Supuestamente, establecer un puntero a NULL
después de free
se supone que previene el temido problema "doble gratis" cuando el mismo valor del puntero se pasa a free
más de una vez. Sin embargo, en realidad, en 9 de cada 10 casos, el verdadero problema "doblemente libre" ocurre cuando diferentes objetos de puntero que tienen el mismo valor de puntero se utilizan como argumentos de forma free
. Ni que decir tiene que establecer un puntero a NULL
después de free
logra absolutamente nada para evitar el problema en tales casos.
Por supuesto, es posible ejecutar el problema "doble gratis" cuando se utiliza el mismo objeto puntero como argumento para free
. Sin embargo, en realidad situaciones como esa normalmente indican un problema con la estructura lógica general del código, no una mera "libertad doble" accidental. Una forma adecuada de tratar el problema en tales casos es revisar y repensar la estructura del código para evitar la situación cuando el mismo puntero se free
más de una vez. En tales casos, establecer el puntero a NULL
y considerar que el problema es "fijo" no es más que un intento de eliminar 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 es NULL
o no NULL
, está perfectamente bien establecer el valor del puntero en NULL
después de free
. Pero como regla general de "buenas prácticas" (como en "siempre coloque su puntero a NULL
después de ser free
"), es, una vez más, una falsificación bien conocida y bastante inútil, seguido a menudo por razones puramente religiosas, vudú. .
Esto (puede) ser realmente importante. Aunque libere la memoria, una parte posterior del programa podría asignar algo nuevo que aterrice en el espacio. Su antiguo puntero apunta a un pedazo válido 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 hará referencia a la eliminación de 0x0 y se bloqueará allí mismo, lo cual es fácil de depurar. Los punteros aleatorios que apuntan a la memoria aleatoria son difíciles de depurar. Obviamente no es necesario, pero es por eso que está en un documento de mejores prácticas.
Esto podría ser más un argumento para inicializar todos los apuntadores 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 antiguo nPtr
, por lo que podría contener un puntero aparentemente válido. Asignar a *p
puede sobrescribir todo tipo de cosas no relacionadas y dar lugar a errores desagradables. Especialmente si el compilador inicializa las variables locales con cero en el modo de depuración, pero no una vez que las optimizaciones están activadas. Por lo tanto, las compilaciones de depuración no muestran signos del error, mientras que las versiones de lanzamiento explotan aleatoriamente ...
Esto se considera una buena práctica para evitar sobrescribir la memoria. En la función anterior, no es necesario, 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
DEBUG_VERSION le permite liberar el perfil en el código de depuración, pero ambos son funcionalmente iguales.
Editar : Añadido do ... mientras que como se sugiere a continuación, gracias.
Hay dos razones:
Evita los bloqueos cuando se libera por partida doble
Escrito por 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 bastante mal, el sistema operativo intenta liberar algo de memoria ya liberada y generalmente segfault. Entonces, 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 para que no tenga que escribir la instrucción if. No soy realmente un gurú del sistema operativo, pero estoy bastante bien, incluso ahora la mayoría de los sistemas operativos se bloquean con doble libre.Esa es también la razón principal por la cual todos los lenguajes con recolección de basura (Java, dotnet) estaban tan orgullosos de no tener este problema y no tener que dejar al desarrollador la administración de la memoria como un todo.
Evite el uso de 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 colgando. 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 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 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 libera 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.
La configuración de un puntero a NULL es para proteger contra la llamada doble libre: una situación en la que se llama a free () 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, acumula corrupción o bloquea el programa inmediatamente. Llamar gratis () para un puntero NULL no hace nada y, por lo tanto, está garantizado que es seguro.
Entonces, la mejor práctica a menos que ahora esté seguro de que el puntero abandona el alcance inmediatamente o muy pronto después de free () es establecer ese puntero en NULL, de modo que incluso si se llama de nuevo a free (), ahora se llama para un puntero NULL y un comportamiento indefinido es evadido
La idea detrás de esto es detener la reutilización accidental del puntero liberado.
La idea es que si intenta desreferenciar el puntero que ya no es válido después de liberarlo, quiere fallar duro (segfault) en lugar de silenciosa y misteriosamente.
Pero ten cuidado. No todos los sistemas causan una segfault si desreferencia NULL. En (al menos algunas versiones de) AIX, * (int *) 0 == 0, y Solaris tiene compatibilidad opcional con esta "característica" de AIX.
La mayoría de las respuestas se han centrado en evitar un doble gratis, pero establecer el puntero a 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, puede terminar con un error donde intenta utilizar el puntero después de liberar y corromper algunas otras variables, y luego su programa entra en un estado desconocido y todo tipo de cosas malas pueden suceder (bloqueo si tenga suerte, corrupción de datos si no tiene suerte). Si había establecido el puntero a NULL después de libre, cualquier intento de leer / escribir a través de ese puntero más tarde daría como resultado una segfault, que generalmente es preferible a la corrupción aleatoria de la memoria.
Por ambas razones, puede ser una buena idea establecer el puntero a NULL después de free (). Sin embargo, no siempre es necesario. Por ejemplo, si la variable del puntero sale del alcance inmediatamente después de free (), no hay muchas razones para establecerlo en NULL.
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();
Al marcar explícitamente el puntero como NULL después de liberarlo, se permite este tipo de uso en C / C ++.
Para resumir: no quiere accidentalmente (por error) acceder a la dirección que ha liberado. Porque, cuando liberas la dirección, permites que esa dirección en el montón se asigne a alguna otra aplicación.
Sin embargo, si no establece el puntero a NULL y, por error, intenta desviar el puntero o cambiar el valor de esa dirección; USTED PUEDE TODAVÍA HACERLO. PERO NO ALGO QUE USTED DESEA LÓGICAMENTE.
¿Por qué todavía puedo acceder a la ubicación de memoria que he liberado? Porque: es posible que tenga libre la memoria, pero la variable del puntero todavía tenía información sobre la dirección de la memoria del montón. Entonces, como una estrategia de defensa, por favor ajústelo a NULL.
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 la memoria, este puntero se establezca en NULL. Este puede no ser el caso para alguna biblioteca implementada para la plataforma.
así que siempre ve por
free(ptr);
ptr = NULL;
Si llega al puntero que ha estado libre () d, podría romperse o no. Esa memoria puede ser reasignada a otra parte de tu programa y luego obtienes corrupción de memoria,
Si configura el puntero a NULL, si accede a él, el programa siempre se bloquea con segfault. No más, a veces funciona '''', no más, se bloquea de manera impredecible ''''. Es mucho más fácil de depurar.
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 aconsejable anular la variable del 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 denomina 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 q apunta (dado 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.
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 bastante mal, el sistema operativo intenta liberar algo de memoria ya liberada y generalmente segfault. Entonces, 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 para que no tenga que escribir la instrucción if. No soy realmente un gurú del sistema operativo, pero estoy bastante bien, incluso ahora la mayoría de los sistemas operativos se bloquean con doble libre.
Esa es también la razón principal por la que todos los lenguajes con recolección de basura (Java, dotnet) estaban tan orgullosos de no tener este problema y no tener que dejar a los desarrolladores la administración de la memoria como un todo.