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 NULL
no 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.
c
coding-style
malloc
free
heap-memory
Alphaneo
fuente
fuente
ptr == NULL
antes de hacer algo con él. Si no anula sus punteros gratuitos, obtendrá unptr != NULL
puntero inutilizable.Respuestas:
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.
fuente
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.Establecer un puntero para
NULL
despuésfree
es 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
NULL
afterfree
previene el temido problema de "doble libre" cuando se pasa el mismo valor de puntero afree
má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 parafree
. No hace falta decir que establecer un puntero enNULL
después nofree
logra 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 afree
más de una vez. En tales casos, configurar el punteroNULL
y 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
NULL
o noNULL
, está perfectamente bien establecer el valor del puntero enNULL
afterfree
. Pero como regla general de "buenas prácticas" (como en "siempre ponga el puntero enNULL
despuésfree
") 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ú.fuente
foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;
. En este caso, el establecimientobar
deNULL
después de la llamada afree
causará la función de pensar que no tenía un bar y devolver el valor incorrecto!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.
fuente
free
, pero esto realmente tiene sentido.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:
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.
fuente
do { } while(0)
bloque para queif(x) myfree(x); else dostuff();
no se rompa.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.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.
fuente
Establecer el puntero en la
free
memoria '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
nPtr
está fuera de alcance justo despuésnPtr = NULL
, no parece haber una razón para configurarloNULL
. 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
NULL
después de cada libre, pero tiene el potencial de señalar grandes problemas.fuente
El error más común en c es el doble gratis. Básicamente haces algo así
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 memoriaTambié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.
fuente
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 }
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íafree(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 punterosNULL
, no se les debe forzar.free
" (junto con cosas tales como "emitir el resultado de funciones de asignación de memoria" o "uso irreflexivo de nombres de tipo consizeof
").La idea detrás de esto es detener la reutilización accidental del puntero liberado.
fuente
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.
fuente
Del estándar ANSI C:
"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:
fuente
NULL
para evitar errores de enmascaramiento. stackoverflow.com/questions/1025589/… Parece que en cualquier caso se ocultan algunos errores.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.
fuente
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
fuente
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.
fuente
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:
Marcar explícitamente el puntero como NULL después de liberarlo permite este tipo de uso en C / C ++.
fuente
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:
p
termina en el mismo lugar en la pila que el primeronPtr
, 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 ...fuente
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
fuente
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.
fuente
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.
fuente
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
fuente
Hay dos razones:
Evite bloqueos cuando se libera doblemente
Escrito por RageZ en una pregunta duplicada .
Evite usar punteros ya liberados
Escrito por Martin v. Löwis en otra respuesta .
fuente
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 dicetst.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.
fuente
Siempre es aconsejable declarar una variable de puntero con NULL como,
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: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
fuente
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.
fuente