realloc en c
realloc() indicadores colgantes y comportamiento indefinido (3)
Cuando liberas memoria, ¿qué sucede con los punteros que apuntan a esa memoria? ¿Se vuelven inválidos inmediatamente? ¿Qué pasa si luego vuelven a ser válidos?
Ciertamente, el caso habitual de que un puntero deje de ser válido y vuelva a ser "válido" sería otro objeto que se asigna a lo que es la memoria utilizada anteriormente, y si utiliza el puntero para acceder a la memoria, obviamente se trata de un comportamiento indefinido. La memoria del puntero colgante sobrescribe la lección 1, prácticamente.
Pero, ¿qué pasa si la memoria vuelve a ser válida para la misma asignación? Solo hay una forma estándar para que eso suceda: realloc()
. Si tiene un puntero a algún lugar dentro de un bloque de memoria malloc()
''d en offset > 1
, entonces use realloc()
para reducir el bloqueo a menos de su desplazamiento, obviamente su puntero se vuelve inválido. Si luego usa realloc()
vuelva a crecer el bloque de nuevo para cubrir al menos el tipo de objeto al que apunta el puntero colgante, y en ninguno de los casos realloc()
mueve el bloque de memoria, ¿el puntero colgante es válido nuevamente?
Este es un caso de esquina que realmente no sé cómo interpretar los estándares C o C ++ para resolverlo. El siguiente es un programa que lo muestra.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
static const char s_message[] = "hello there";
static const char s_kitty[] = "kitty";
char *string = malloc(sizeof(s_message));
if (!string)
{
fprintf(stderr, "malloc failed/n");
return 1;
}
memcpy(string, s_message, sizeof(s_message));
printf("%p %s/n", string, string);
char *overwrite = string + 6;
*overwrite = ''/0'';
printf("%p %s/n", string, string);
string[4] = ''/0'';
char *new_string = realloc(string, 5);
if (new_string != string)
{
fprintf(stderr, "realloc #1 failed or moved the string/n");
free(new_string ? new_string : string);
return 1;
}
string = new_string;
printf("%p %s/n", string, string);
new_string = realloc(string, 6 + sizeof(s_kitty));
if (new_string != string)
{
fprintf(stderr, "realloc #2 failed or moved the string/n");
free(new_string ? new_string : string);
return 1;
}
// Is this defined behavior, even though at one point,
// "overwrite" was a dangling pointer?
memcpy(overwrite, s_kitty, sizeof(s_kitty));
string[4] = s_message[4];
printf("%p %s/n", string, string);
free(string);
return 0;
}
Cuando liberas memoria, ¿qué sucede con los punteros que apuntan a esa memoria? ¿Se vuelven inválidos inmediatamente?
Sí definitivamente. De la sección 6.2.4 del estándar C:
La duración de un objeto es la porción de ejecución del programa durante la cual se garantiza que el almacenamiento está reservado para él. Existe un objeto, tiene una dirección constante y conserva su último valor almacenado a lo largo de su vida útil. Si se hace referencia a un objeto fuera de su tiempo de vida, el comportamiento no está definido. El valor de un puntero se vuelve indeterminado cuando el objeto al que apunta (o simplemente pasado) alcanza el final de su vida útil.
Y de la sección 7.22.3.5:
La función realloc desasigna el objeto antiguo apuntado por ptr y devuelve un puntero a un nuevo objeto que tiene el tamaño especificado por tamaño. El contenido del nuevo objeto será el mismo que el del objeto anterior antes de la desasignación, hasta el menor de los tamaños nuevo y antiguo. Cualquier byte en el nuevo objeto más allá del tamaño del objeto antiguo tiene valores indeterminados.
Tenga en cuenta la referencia al objeto antiguo y al objeto nuevo ... según el estándar, lo que obtiene de realloc es un objeto diferente de lo que tenía antes; no es diferente de hacer un free
y luego un malloc
, y no hay garantía de que los dos objetos tengan la misma dirección, incluso si el nuevo tamaño es <= el antiguo tamaño ... y en implementaciones reales a menudo no lo harán porque objetos de diferentes tamaños se extraen de diferentes listas libres.
¿Qué pasa si luego vuelven a ser válidos?
No hay tal animal. La validez no es un evento que tiene lugar, es una condición abstracta colocada por el estándar C. Puede que los punteros funcionen en alguna implementación, pero todas las apuestas se desactivan una vez que liberas la memoria que señalan.
Pero, ¿qué pasa si la memoria vuelve a ser válida para la misma asignación? Solo hay una forma estándar para que eso suceda: realloc ()
Lo sentimos, no, el estándar C no contiene ningún lenguaje a tal efecto.
Si luego usa realloc (), vuelva a crecer el bloque de nuevo para cubrir al menos el tipo de objeto al que apunta el puntero colgante, y en ninguno de los casos realloc () mueve el bloque de memoria
No puede saber si lo hará ... el estándar no garantiza tal cosa. Y notablemente, cuando reallocas a un tamaño más pequeño, la mayoría de las implementaciones modifican la memoria inmediatamente después del bloque acortado; volver a colocar el original tendrá algo de basura en la parte añadida, no será lo que era antes de que se redujera. En algunas implementaciones, algunos tamaños de bloque se mantienen en listas para ese tamaño de bloque; reasignar a un tamaño diferente le dará una memoria totalmente diferente. Y en un programa con múltiples hilos, cualquier memoria liberada se puede asignar en un hilo diferente entre los dos reallocs, en cuyo caso el realloc para un tamaño mayor se verá obligado a mover el objeto a una ubicación diferente.
¿El puntero colgante es válido de nuevo?
Véase más arriba; inválido no es válido; no hay vuelta atrás.
Este es un caso de esquina que realmente no sé cómo interpretar los estándares C o C ++ para resolverlo.
No es ningún tipo de caso de esquina y no sé lo que está viendo en el estándar, lo que es bastante claro que la memoria liberada tiene un contenido indeterminado y que los valores de los punteros ao en él también son indeterminados, y no hace afirman que son mágicamente restaurados por un realloc posterior.
Tenga en cuenta que los compiladores optimizadores modernos están escritos para conocer el comportamiento indefinido y aprovecharlo. Tan pronto como vuelva a colocar la cadena, la overwrite
no es válida, y el compilador puede destruirla ... por ejemplo, podría estar en un registro que el compilador reasigne para el paso de parámetros o temporarios. Si cualquier compilador hace esto, puede hacerlo, precisamente porque el estándar es bastante claro acerca de los punteros en los objetos que se vuelven inválidos cuando termina la vida del objeto.
Depende de tu definición de "válido". Usted ha descrito perfectamente la situación. Si quiere considerar eso "válido", entonces es válido. Si no quiere considerar eso "válido", entonces no es válido.
Si luego usa realloc (), vuelva a crecer el bloque de nuevo para cubrir al menos el tipo de objeto al que apunta el puntero colgante, y en ninguno de los casos realloc () mueve el bloque de memoria, ¿el puntero colgante es válido nuevamente?
No. A menos que realloc()
devuelva un puntero nulo, la llamada termina el tiempo de vida del objeto asignado, lo que implica que todos los punteros que apuntan hacia él dejan de ser válidos. Si realloc()
tiene éxito, devuelve la dirección de un nuevo objeto.
Por supuesto, podría suceder que sea la misma dirección que la anterior. En ese caso, usar un puntero no válido para el objeto antiguo para acceder al nuevo generalmente funcionará en implementaciones no optimizadoras del lenguaje C.
Sin embargo, aún sería un comportamiento indefinido y podría fallar con la optimización agresiva de los compiladores.
El lenguaje C es poco sólido y, en general, depende del programador mantener sus invariantes. De lo contrario, se romperá el contrato implícito con el compilador y puede generar código incorrecto.