c language-lawyer glibc

¿Realloc(p, 0) realmente implica free(p) en glibc?



language-lawyer (4)

Aunque mi interpretación del caso " NULL devuelto" parece ser correcta (consulte mi edición a continuación), los desarrolladores de glibc decided mantenerlo en consonancia con el estándar C89 anterior y rechazaron cumplir con C99 / C11:

No hay forma de que esto sea cambiado. Así es como se ha implementado para siempre. C debería documentar la práctica existente. Cambiarlo significaría introducir fugas de memoria.

Además, la indicación de mcheck fue engañosa, ya que otro caso de prueba ha demostrado que la memoria se libera efectivamente por realloc :

#include <malloc.h> #include <stdio.h> #include <stdlib.h> int main(void) { int *p, *q; p = malloc(20 * sizeof(int)); malloc_stats(); putchar(''/n''); q = realloc(p, 0); malloc_stats(); return 0; }

Aquí, la salida es:

$ gcc check.c $ ./a.out Arena 0: system bytes = 135168 in use bytes = 96 Total (incl. mmap): system bytes = 135168 in use bytes = 96 max mmap regions = 0 max mmap bytes = 0 Arena 0: system bytes = 135168 in use bytes = 0 Total (incl. mmap): system bytes = 135168 in use bytes = 0 max mmap regions = 0 max mmap bytes = 0

EDITAR:

Como se señaló en el comment , el Grupo de trabajo ISO / IEC tuvo alguna discusión, materializada como Informe de Defectos # 400 . Es probable que los cambios propuestos permitan la práctica existente de glibc en la futura revisión de la Norma C (o posiblemente como Corrigendum Técnico 1 para C11).

Lo que realmente me gusta de DR # 400 es la propuesta de:

Agregue a la subsección 7.31.12 un nuevo párrafo (párrafo 2):

Invocar realloc con un argumento de tamaño igual a cero es una característica obsoleta.

Descubrí que algunas personas y referencias como libros afirman que si p != NULL y p proceden de la asignación anterior (por ejemplo, por malloc ), entonces realloc(p, 0) es equivalente a free(p) en GNU / Linux. Para respaldar esta tesis, el man realloc afirma exactamente de esa manera (el énfasis es mío):

La función realloc () cambia el tamaño del bloque de memoria apuntado por ptr a tamaño bytes. Los contenidos no cambiarán en el rango desde el inicio de la región hasta el mínimo de los tamaños antiguos y nuevos. Si el nuevo tamaño es más grande que el anterior, la memoria agregada no se inicializará. Si ptr es NULL, entonces la llamada es equivalente a malloc (tamaño), para todos los valores de tamaño; Si el tamaño es igual a cero y ptr no es NULL, entonces la llamada es equivalente a free (ptr) . A menos que ptr sea NULL, debe haber sido devuelto por una llamada anterior a malloc (), calloc () o realloc (). Si el área señalada fue movida, se realiza un libre (ptr).

Como puede encontrar en esta pregunta , el Estándar C no define con precisión lo que debería suceder y el comportamiento real se define por la implementación. Más específicamente:

El C11 §7.22.3 / p1 funciones de gestión de memoria dice:

Si el tamaño del espacio solicitado es cero, el comportamiento está definido por la implementación: se devuelve un puntero nulo o el comportamiento es como si el tamaño fuera un valor distinto de cero, excepto que el puntero devuelto no se usará para acceder a un objeto .

y C11 §7.22.3.5 La función realloc contiene:

3) (...) Si no se puede asignar memoria para el nuevo objeto, el objeto antiguo no se desasigna y su valor no cambia .

4) La función realloc devuelve un puntero al nuevo objeto (que puede tener el mismo valor que un puntero al objeto anterior), o un puntero nulo si no se puede asignar el nuevo objeto .

Escribí un código básico para averiguar el comportamiento real con la ayuda de mcheck , el comprobador de memoria, que se suministra con glibc :

#include <mcheck.h> #include <stdio.h> #include <stdlib.h> int main(void) { int a = 5; int *p, *q; mtrace(); p = malloc(sizeof(int)); q = &a; printf("%p/n", (void *) p); printf("%p/n", (void *) q); q = realloc(p, 0); printf("%p/n", (void *) p); printf("%p/n", (void *) q); return 0; }

y los resultados son:

$ gcc -g check.c $ export MALLOC_TRACE=report $ ./a.out 0xfd3460 0x7ffffbc955cc 0xfd3460 (nil) [grzegorz@centos workspace]$ mtrace a.out report Memory not freed: ----------------- Address Size Caller 0x0000000000fd3460 0x4 at /home/grzegorz/workspace/check.c:12

Como puede ver, q se estableció en NULL . Parece que free() no fue realmente llamado. De hecho, no puede ser a menos que mi interpretación sea incorrecta: dado que realloc ha devuelto el puntero NULL , no se pudo asignar el nuevo objeto, lo que implica que:

el objeto antiguo no se desasigna y su valor no se modifica

¿Es esto correcto?


Como el comportamiento de realloc() cuando se pasa un tamaño de 0 se define la implicación ...

Si el tamaño del espacio solicitado es cero, el comportamiento está definido por la implementación: se devuelve un puntero nulo o el comportamiento es como si el tamaño fuera un valor distinto de cero, excepto que el puntero devuelto no se usará para acceder a un objeto .

... el equivalente portátil a

void * p = malloc(1); free(p);

necesita ser

void * p = malloc(1); p = realloc(p, 0) free(p); /* because of the part after the "or" as quoted above.

El balance de la memoria debe ser incluso después.

Actualización que cubre el realloc() "error" de realloc() :

void * p = malloc(1); { void * q = realloc(p, 0); p = q ?q :p; } free(p); /* because of the part after the "or" as quoted above.



Edit: su glibc parece ser un pre-2.18, en 2.18 se corrigió un error en mtrace (ver here ). En un informe de 2.20 glibc, su programa de prueba informa: "No hay pérdidas de memoria".

Se llama free en glibc. De las fuentes de la corriente glibc 2.21 ( here y here ):

/* REALLOC_ZERO_BYTES_FREES should be set if a call to realloc with zero bytes should be the same as a call to free. This is required by the C standard. Otherwise, since this malloc returns a unique pointer for malloc(0), so does realloc(p, 0). */ #ifndef REALLOC_ZERO_BYTES_FREES #define REALLOC_ZERO_BYTES_FREES 1 #endif void * __libc_realloc (void *oldmem, size_t bytes) { mstate ar_ptr; INTERNAL_SIZE_T nb; /* padded request size */ void *newp; /* chunk to return */ void *(*hook) (void *, size_t, const void *) = atomic_forced_read (__realloc_hook); if (__builtin_expect (hook != NULL, 0)) return (*hook)(oldmem, bytes, RETURN_ADDRESS (0)); #if REALLOC_ZERO_BYTES_FREES if (bytes == 0 && oldmem != NULL) { __libc_free (oldmem); return 0; } #endif