sirven que punteros puntero para operaciones los lenguaje ejemplos declaracion con cadenas aritmetica c pointers free

que - punteros a cadenas



¿Debería uno realmente establecer punteros a `NULL` después de liberarlos? (10)

Parece que hay dos argumentos por los que uno debe establecer un puntero a NULL después de liberarlos.

Evite chocar cuando doble punteros de liberación.

Corto: Llamar free() por segunda vez, por accidente, no se cuelga cuando está establecido en NULL .

  • Casi siempre esto enmascara una falla lógica porque no hay razón para llamar a free() por segunda vez. Es más seguro dejar que la aplicación se cuelgue y poder arreglarlo.

  • No se garantiza que bloquee porque a veces se asigna memoria nueva en la misma dirección.

  • La doble libre ocurre principalmente cuando hay dos punteros apuntando a la misma dirección.

Los errores lógicos pueden conducir a la corrupción de datos también.

Evita reutilizar punteros liberados

Corto: el acceso a punteros liberados puede causar daños en los datos si malloc() asigna memoria en el mismo lugar a menos que el puntero liberado esté establecido en NULL

  • No hay garantía de que el programa se bloquee al acceder al puntero NULL , si el desplazamiento es lo suficientemente grande ( someStruct->lastMember , theArray[someBigNumber] ). En lugar de bloquearse habrá corrupción de datos.

  • Establecer el puntero a NULL no puede resolver el problema de tener un puntero diferente con el mismo valor de puntero.

Las preguntas

Aquí hay una publicación que establece ciegamente un puntero a NULL después de la liberación .

  • ¿Cuál es más difícil de depurar?
  • ¿Hay alguna posibilidad de atrapar ambos?
  • ¿Cuán probable es que esos errores conduzcan a la corrupción de datos en lugar de fallar?

Siéntase libre de expandir esta pregunta.


No hay garantía de que el programa se bloquee al acceder al puntero NULL.

Tal vez no por el estándar, pero sería difícil encontrar una implementación que no la defina como una operación ilegal que causa un bloqueo o una excepción (según corresponda al entorno de tiempo de ejecución).


Ambos son muy importantes ya que tratan con un comportamiento indefinido. No debe dejar ninguna forma de comportamiento indefinido en su programa. Ambos pueden provocar bloqueos, datos corruptos, errores sutiles, cualquier otra mala consecuencia.

Ambos son bastante difíciles de depurar. Ambos no se pueden evitar con seguridad, especialmente en el caso de estructuras de datos complejas. De todos modos, estás mucho mejor si sigues las siguientes reglas:

  • siempre inicialice los punteros - configúrelos en NULL o en alguna dirección válida
  • después de llamar a free () establece el puntero a NULL
  • verifique cualquier puntero que posiblemente sea NULL para ser NULL antes de desreferenciarlos.

El segundo es mucho más importante: reutilizar un puntero liberado puede ser un error sutil. Su código sigue funcionando correctamente, y luego se cuelga sin razón aparente porque un código aparentemente no relacionado escribió en la memoria que el puntero reutilizado está apuntando.

Una vez tuve que trabajar en un programa realmente problemático que alguien más escribió. Mis instintos me dijeron que muchos de los errores estaban relacionados con intentos descuidados de seguir usando punteros después de liberar la memoria; Modifiqué el código para configurar los punteros a NULL después de liberar la memoria, y bam , comenzaron a aparecer las excepciones del puntero nulo. Después de arreglar todas las excepciones del puntero nulo, de repente el código era mucho más estable.

En mi propio código, solo llamo a mi propia función que es un contenedor alrededor de free (). Toma un puntero a un puntero y anula el puntero después de liberar la memoria. Y antes de que llame gratis, llama a Assert(p != NULL); por lo que aún detecta intentos de duplicar el mismo puntero.

Mi código también hace otras cosas, como (en la creación DEBUG solamente) llenar la memoria con un valor obvio inmediatamente después de asignarla, haciendo lo mismo antes de llamar a free() en caso de que haya una copia del puntero, etc. Detalles aquí .

EDITAR: según una solicitud, aquí hay un código de ejemplo.

void FreeAnything(void **pp) { void *p; AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value"); if (!pp) return; p = *pp; AssertWithMessage(p != NULL, "attempt to free a null pointer"); if (!p) return; free(p); *pp = NULL; } // FOO is a typedef for a struct type void FreeInstanceOfFoo(FOO **pp) { FOO *p; AssertWithMessage(pp != NULL, "need pointer-to-pointer, got null value"); if (!pp) return; p = *pp; AssertWithMessage(p != NULL, "attempt to free a null FOO pointer"); if (!p) return; AssertWithMessage(p->signature == FOO_SIG, "bad signature... is this really a FOO instance?"); // free resources held by FOO instance if (p->storage_buffer) FreeAnything(&p->storage_buffer); if (p->other_resource) FreeAnything(&p->other_resource); // free FOO instance itself free(p); *pp = NULL; }

Comentarios:

Puede ver en la segunda función que necesito verificar los dos punteros de recursos para ver si no son nulos, y luego llamar a FreeAnything() . Esto se debe a assert() que se quejará de un puntero nulo. Tengo esa afirmación para detectar un intento de duplicar, pero no creo que haya atrapado muchos errores para mí; si desea dejar de lado las afirmaciones, puede omitir el cheque y simplemente llamar a FreeAnything() . Aparte de la afirmación, no pasa nada malo cuando intenta liberar un puntero nulo con FreeAnything() porque comprueba el puntero y simplemente lo devuelve si ya era nulo.

Mis nombres de funciones reales son bastante más escuetos, pero traté de elegir nombres autodocumentados para este ejemplo. Además, en mi código actual, tengo un código de depuración que llena los búferes con el valor 0xDC antes de llamar a free() modo que si tengo un puntero adicional a esa misma memoria (uno que no se anula) se vuelve realmente es obvio que los datos a los que apunta son datos falsos. Tengo una macro, DEBUG_ONLY() , que compila en DEBUG_ONLY() en una compilación sin depuración; y un macro FILL() que hace un sizeof() en una estructura. Estos dos funcionan igual de bien: sizeof(FOO) o sizeof(*pfoo) . Así que aquí está la macro FILL() :

#define FILL(p, b) / (memset((p), b, sizeof(*(p)))

Aquí hay un ejemplo del uso de FILL() para poner los valores 0xDC antes de llamar:

if (p->storage_buffer) { DEBUG_ONLY(FILL(pfoo->storage_buffer, 0xDC);) FreeAnything(&p->storage_buffer); }

Un ejemplo de usar esto:

PFOO pfoo = ConstructNewInstanceOfFoo(arg0, arg1, arg2); DoSomethingWithFooInstance(pfoo); FreeInstanceOfFoo(&pfoo); assert(pfoo == NULL); // FreeInstanceOfFoo() nulled the pointer so this never fires


En C ++ podría detectar ambos implementando su propio puntero inteligente (o derivado de implementaciones existentes) e implementando algo como:

void release() { assert(m_pt!=NULL); T* pt = m_pt; m_pt = NULL; free(pt); } T* operator->() { assert(m_pt!=NULL); return m_pt; }

Alternativamente, en C, al menos podría proporcionar dos macros para el mismo efecto:

#define SAFE_FREE(pt) / assert(pt!=NULL); / free(pt); / pt = NULL; #define SAFE_PTR(pt) assert(pt!=NULL); pt


En realidad, no hay una parte "más importante" sobre cuál de los dos problemas está tratando de evitar. Realmente, realmente necesita evitar ambos si desea escribir software confiable. También es muy probable que cualquiera de los anteriores conduzca a la corrupción de datos, que su servidor web se inicie y que otros se diviertan en esa línea.

También hay otro paso importante a tener en cuenta: configurar el puntero a NULL después de liberarlo es solo la mitad del trabajo. Idealmente, si está utilizando esta expresión idiomática, también debe ajustar el acceso al puntero en algo como esto:

if (ptr) memcpy(ptr->stuff, foo, 3);

Simplemente establecer el puntero en NULL solo bloqueará el programa en lugares inoportunos, lo que probablemente sea mejor que corromper los datos en silencio, pero aún no es lo que desea.


Estos problemas son a menudo solo síntomas de un problema mucho más profundo. Esto puede ocurrir para todos los recursos que requieren adquisición y una versión posterior, por ejemplo, memoria, archivos, bases de datos, conexiones de red, etc. El problema principal es que ha perdido la pista de las asignaciones de recursos por una estructura de código faltante, lanzando mallocs aleatorios y libera todo el código base

Organice el código alrededor de DRY - No se repita. Mantenga las cosas relacionadas juntas. Haz una sola cosa y hazlo bien. El "módulo" que asigna un recurso es responsable de liberarlo y debe proporcionar una función para hacerlo que también cuide los indicadores. Para cualquier recurso específico, tiene exactamente un lugar donde se asigna y un lugar donde se publica, ambos muy juntos.

Supongamos que quiere dividir una cadena en subcadenas. Utilizando directamente malloc (), su función tiene que preocuparse por todo: analizar la cadena, asignar la cantidad correcta de memoria, copiar las subcadenas allí, yy. Haga que la función sea lo suficientemente complicada, y no es la pregunta si perderá la pista de los recursos, sino cuándo.

Su primer módulo se ocupa de la asignación de memoria real:

void *MemoryAlloc (size_t size) void MemoryFree (void *ptr)

Hay tu único lugar en toda la base de código donde se llaman malloc () y free ().

Entonces tenemos que asignar cadenas:

StringAlloc (char **str, size_t len) StringFree (char **str)

Se ocupan de que se necesite len + 1 y que el puntero se establezca en NULL cuando se libere. Proporcione otra función para copiar una subcadena:

StringCopyPart (char **dst, const char *src, size_t index, size_t len)

Se ocupará de que index y len estén dentro de la cadena src y la modifique cuando sea necesario. Llamará a StringAlloc para dst, y le importará que dst se termine correctamente.

Ahora puedes escribir tu función dividida. Ya no tiene que preocuparse por los detalles de bajo nivel, simplemente analice la cadena y elimine las subcadenas. La mayor parte de la lógica ahora está en el módulo al que pertenece, en lugar de mezclarse en una gran monstruosidad.

Por supuesto, esta solución tiene sus propios problemas. Proporciona capas de abstracción, y cada capa, mientras resuelve otros problemas, viene con su propio conjunto de ellas.


La respuesta depende de (1) tamaño del proyecto, (2) vida esperada de su código, (3) tamaño del equipo. En un proyecto pequeño con una corta vida útil, puede omitir el establecimiento de punteros a NULL, y simplemente realizar la depuración.

En un proyecto grande y de larga duración, existen buenas razones para establecer punteros a NULL: (1) La programación defensiva siempre es buena. Su código podría estar bien, pero el principiante de al lado podría tener dificultades con los punteros (2) Mi creencia personal es que todas las variables deben contener solo valores válidos en todo momento. Después de eliminar / libre, el puntero ya no es un valor válido, por lo que debe eliminarse de esa variable. Reemplazarlo por NULL (el único valor de puntero que siempre es válido) es un buen paso. (3) El código nunca muere. Siempre se reutiliza, y a menudo de maneras que no has imaginado en el momento en que lo escribiste. Su segmento de código podría terminar siendo compilado en un contexto C ++, y probablemente se mueva a un destructor o un método que sea llamado por un destructor. Las interacciones de los métodos virtuales y objetos que están en proceso de destrucción son sutiles trampas incluso para programadores con mucha experiencia. (4) Si su código termina siendo utilizado en un contexto de subprocesos múltiples, algún otro subproceso podría leer esa variable e intentar acceder a ella. Tales contextos a menudo surgen cuando el código heredado se envuelve y reutiliza en un servidor web. Así que una forma aún mejor de liberar memoria (desde un punto de vista paranoico) es (1) copiar el puntero a una variable local, (2) establecer la variable original a NULL, (3) eliminar / liberar la variable local.


Si el puntero se va a volver a utilizar, se debe volver a establecer en 0 (NULO) después de su uso, incluso si el objeto al que apunta no se libera del montón. Esto permite una comprobación válida contra NULL como si (p) {// hiciera algo}. Además, solo porque libere un objeto a cuya dirección apunta el puntero no significa que el puntero esté configurado 0 después de invocar la palabra clave delete o la función free en absoluto.

Si el puntero se usa una vez y es parte de un alcance que lo hace local, entonces no es necesario establecerlo en NULL, ya que se eliminará de la pila después de que la función regrese.

Si el puntero es un miembro (struct o clase), debe establecerlo en NULL después de liberar el objeto u objetos en un puntero doble nuevamente para una comprobación válida contra NULL.

Hacer esto lo ayudará a aliviar los dolores de cabeza de punteros inválidos como ''0xcdcd ...'' y así sucesivamente. Entonces, si el puntero es 0, entonces sabrá que no está apuntando a una dirección y puede asegurarse de que el objeto se libera del montón.


Si no establece el puntero a NULL, existe una posibilidad no demasiado pequeña, que su aplicación continúe ejecutándose en un estado indefinido y falle más tarde en un punto completamente no relacionado. Luego pasará mucho tiempo con la depuración de un error inexistente antes de descubrir, que es una corrupción de la memoria de antes.

Establecí el puntero en NULO porque hay más posibilidades de que llegue al lugar correcto del error antes que si no lo estableciera en NULO. Aún no se ha pensado en el error lógico de liberar memoria por segunda vez y el error de que su aplicación NO falla en el acceso de puntero nulo con una compensación lo suficientemente grande es, en mi opinión, completamente académico aunque no imposible.

Conclusión: me gustaría establecer el puntero a NULL.


Yo no hago esto No recuerdo particularmente ningún error que hubiese sido más fácil de tratar si lo hubiera hecho. Pero realmente depende de cómo escribas tu código. Hay aproximadamente tres situaciones en las que libero algo:

  • Cuando el puntero que lo contiene está a punto de salir del alcance, o es parte de un objeto que está a punto de quedar fuera del alcance o ser liberado.
  • Cuando estoy reemplazando el objeto por uno nuevo (como con la reasignación, por ejemplo).
  • Cuando estoy liberando un objeto que está opcionalmente presente.

En el tercer caso, establece el puntero a NULL. Eso no es específicamente porque lo estás liberando, es porque lo que sea que sea es opcional, por lo que, por supuesto, NULL tiene un valor especial que significa "No tengo uno".

En los primeros dos casos, establecer el puntero a NULL me parece estar ocupado sin ningún propósito particular:

int doSomework() { char *working_space = malloc(400*1000); // lots of work free(working_space); working_space = NULL; // wtf? In case someone has a reference to my stack? return result; } int doSomework2() { char * const working_space = malloc(400*1000); // lots of work free(working_space); working_space = NULL; // doesn''t even compile, bad luck return result; } void freeTree(node_type *node) { for (int i = 0; i < node->numchildren; ++i) { freeTree(node->children[i]); node->children[i] = NULL; // stop wasting my time with this rubbish } free(node->children); node->children = NULL; // who even still has a pointer to node? // Should we do node->numchildren = 0 too, to keep // our non-existent struct in a consistent state? // After all, numchildren could be big enough // to make NULL[numchildren-1] dereferencable, // in which case we won''t get our vital crash. // But if we do set numchildren = 0, then we won''t // catch people iterating over our children after we''re freed, // because they won''t ever dereference children. // Apparently we''re doomed. Maybe we should just not use // objects after they''re freed? Seems extreme! free(node); } int replace(type **thing, size_t size) { type *newthing = copyAndExpand(*thing, size); if (newthing == NULL) return -1; free(*thing); *thing = NULL; // seriously? Always NULL after freeing? *thing = newthing; return 0; }

Es cierto que NULL-ing el puntero puede hacerlo más obvio si tiene un error donde intenta desreferenciarlo después de liberarlo. La desreferenciación probablemente no dañe inmediatamente si no NULL el puntero, pero está equivocado a la larga.

También es cierto que NULL-ing el puntero oscurece los errores donde se duplica. El segundo libre no causa daño inmediato si NULL el puntero, pero está equivocado a largo plazo (porque delata el hecho de que sus ciclos de vida de objetos están rotos). Puede afirmar que las cosas no son nulas cuando las libera, pero eso da como resultado el siguiente código para liberar una estructura que contiene un valor opcional:

if (thing->cached != NULL) { assert(thing->cached != NULL); free(thing->cached); thing->cached = NULL; } free(thing);

Lo que ese código te dice es que has llegado demasiado lejos. Debería ser:

free(thing->cached); free(thing);

Digo, NULL el puntero si se supone que debe seguir siendo utilizable. Si ya no se puede usar, es mejor no hacerlo falsamente aparente, poniendo un valor potencialmente significativo como NULL. Si desea provocar un error de página, use un valor dependiente de la plataforma que no sea dereferancable, pero que el resto de su código no trate como un valor especial "todo está bien y excelente":

free(thing->cached); thing->cached = (void*)(0xFEFEFEFE);

Si no puede encontrar dicha constante en su sistema, puede asignar una página no legible y / o no grabable, y usar la dirección de esa.