C Language
Gestión de la memoria
Buscar..
Introducción
Para administrar la memoria asignada dinámicamente, la biblioteca C estándar proporciona las funciones malloc()
, calloc()
, realloc()
y free()
. En C99 y posteriores, también hay aligned_alloc()
. Algunos sistemas también proporcionan alloca()
.
Sintaxis
- void * align_alloc (alineación size_t, size_t size); / * Solo desde C11 * /
- void * calloc (size_t nelements, size_t size);
- vacío libre (void * ptr);
- void * malloc (size_t size);
- void * realloc (void * ptr, size_t size);
- void * alloca (size_t size); / * de alloca.h, no estándar, no portátil, peligroso. * /
Parámetros
nombre | descripción |
---|---|
tamaño ( malloc , realloc y aligned_alloc ) | Tamaño total de la memoria en bytes. Para aligned_alloc el tamaño debe ser un múltiplo integral de alineación. |
tamaño ( calloc ) | tamaño de cada elemento |
nelementos | número de elementos |
ptr | puntero a la memoria asignada previamente devuelto por malloc , calloc , realloc o aligned_alloc |
alineación | alineación de la memoria asignada |
Observaciones
Tenga en cuenta que aligned_alloc()
solo se define para C11 o posterior.
Los sistemas como los basados en POSIX proporcionan otras formas de asignar memoria alineada (por ejemplo, posix_memalign()
), y también tienen otras opciones de administración de memoria (por ejemplo, mmap()
).
Liberando memoria
Es posible liberar memoria asignada dinámicamente llamando a free () .
int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL)
{
perror("malloc failed");
return -1;
}
free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
has undefined behavior, until a new value is stored into it. */
/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */
La memoria apuntada por p
se reclama (ya sea por la implementación libc o por el sistema operativo subyacente) después de la llamada a free()
, por lo que acceder al bloque de memoria liberado a través de p
conducirá a un comportamiento indefinido . Los punteros que hacen referencia a elementos de memoria que se han liberado se denominan comúnmente punteros colgantes y presentan un riesgo de seguridad. Además, el estándar C indica que incluso el acceso al valor de un puntero colgante tiene un comportamiento indefinido. Tenga en cuenta que el puntero p
se puede volver a usar como se muestra arriba.
Tenga en cuenta que solo puede llamar a free()
en punteros que hayan sido devueltos directamente desde las funciones malloc()
, calloc()
, realloc()
y aligned_alloc()
, o donde la documentación le indique que la memoria se ha asignado de esa manera (funciones Como strdup ()
son ejemplos notables). Liberando un puntero que es,
- obtenido usando el operador
&
en una variable, o - en medio de un bloque asignado,
está prohibido. Por lo general, su compilador no diagnosticará dicho error, pero llevará la ejecución del programa a un estado indefinido.
Hay dos estrategias comunes para prevenir estos casos de comportamiento indefinido.
Lo primero y preferible es simple: dejar que el propio p
deje de existir cuando ya no se necesita, por ejemplo:
if (something_is_needed())
{
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc failed");
return -1;
}
/* do whatever is needed with p */
free(p);
}
Al llamar a free()
directamente antes del final del bloque contenedor (es decir, el }
), p
sí mismo deja de existir. El compilador dará un error de compilación en cualquier intento de usar p
después de eso.
Un segundo enfoque es invalidar también el puntero después de liberar la memoria a la que apunta:
free(p);
p = NULL; // you may also use 0 instead of NULL
Argumentos a favor de este enfoque:
En muchas plataformas, un intento de anular la referencia a un puntero nulo causará una falla instantánea: falla de segmentación. Aquí, obtenemos al menos un seguimiento de pila que apunta a la variable que se utilizó después de ser liberado.
Sin establecer el puntero a
NULL
tenemos puntero colgante. Es muy probable que el programa aún se bloquee, pero más tarde, porque la memoria a la que apunta el puntero se corromperá silenciosamente. Dichos errores son difíciles de rastrear porque pueden resultar en una pila de llamadas que no guarda relación alguna con el problema inicial.Este enfoque, por lo tanto, sigue el concepto de falla rápida .
Es seguro liberar un puntero nulo. El estándar C especifica que
free(NULL)
no tiene ningún efecto:La función libre hace que el espacio al que apunta ptr se desasigne, es decir, que esté disponible para una asignación adicional. Si ptr es un puntero nulo, no se produce ninguna acción. De lo contrario, si el argumento no coincide con un puntero anterior devuelto por la función
calloc
,malloc
orealloc
, o si el espacio ha sido desasignado por una llamada afree
orealloc
, el comportamiento no está definido.
- A veces no se puede usar el primer enfoque (por ejemplo, la memoria se asigna en una función y se desasigna mucho más tarde en una función completamente diferente)
Asignación de memoria
Asignación estándar
Las funciones de asignación de memoria dinámica C se definen en el encabezado <stdlib.h>
. Si uno desea asignar espacio de memoria para un objeto dinámicamente, se puede usar el siguiente código:
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc() failed");
return -1;
}
Esto calcula el número de bytes que ocupan diez int
en la memoria, luego solicita la cantidad de bytes de malloc
y asigna el resultado (es decir, la dirección de inicio de la porción de memoria que se creó con malloc
) a un puntero llamado p
.
Es una buena práctica usar sizeof
para calcular la cantidad de memoria a solicitar, ya que el resultado de sizeof
se define por implementación (excepto para los tipos de caracteres , que son char
, signed char
y unsigned char
, para los cuales sizeof
se define para dar siempre 1
).
Dado que es posible que malloc
no pueda atender la solicitud, puede devolver un puntero nulo. Es importante comprobar esto para evitar intentos posteriores de anular la referencia al puntero nulo.
La memoria asignada dinámicamente con malloc()
puede redimensionarse con realloc()
o, cuando ya no se necesita, liberar con free()
.
Alternativamente, declarando int array[10];
Asignaría la misma cantidad de memoria. Sin embargo, si se declara dentro de una función sin la palabra clave static
, solo será utilizable dentro de la función en la que se declara y las funciones que llama (porque la matriz se asignará a la pila y el espacio se liberará para su reutilización cuando la función vuelve). Alternativamente, si se define con static
dentro de una función, o si se define fuera de cualquier función, entonces su vida útil es la vida útil del programa. Los punteros también pueden devolverse desde una función, sin embargo, una función en C no puede devolver una matriz.
Memoria de cero
La memoria devuelta por malloc
no puede inicializarse a un valor razonable, y se debe tener cuidado de poner a cero la memoria con memset
o copiar inmediatamente un valor adecuado en ella. Alternativamente, calloc
devuelve un bloque del tamaño deseado donde todos los bits se inicializan a 0
. No es necesario que esto sea igual a la representación de cero en coma flotante o una constante de puntero nulo.
int *p = calloc(10, sizeof *p);
if (p == NULL)
{
perror("calloc() failed");
return -1;
}
Una nota sobre calloc
: la mayoría de las implementaciones (comúnmente utilizadas) optimizarán calloc()
para el rendimiento, por lo que serán más rápidas que llamar a malloc()
, luego memset()
, aunque el efecto neto sea idéntico.
Memoria alineada
C11 introdujo una nueva función aligned_alloc()
que asigna espacio con la alineación dada. Se puede usar si la memoria que se asignará es necesaria para alinearse en ciertos límites que no se pueden satisfacer con malloc()
o calloc()
. malloc()
funciones malloc()
y calloc()
asignan memoria que está adecuadamente alineada para cualquier tipo de objeto (es decir, la alineación es alignof(max_align_t)
). Pero con aligned_alloc()
se pueden solicitar mayores alineaciones.
/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
perror("aligned_alloc()");
return -1;
}
free(ptr);
El estándar C11 impone dos restricciones: 1) el tamaño (segundo argumento) solicitado debe ser un múltiplo integral de la alineación (primer argumento) y 2) el valor de la alineación debe ser una alineación válida respaldada por la implementación. El incumplimiento de cualquiera de ellos da como resultado un comportamiento indefinido .
Reasignación de memoria
Es posible que deba expandir o reducir su espacio de almacenamiento de puntero después de haberle asignado memoria. La función void *realloc(void *ptr, size_t size)
desasigna el objeto antiguo al que apunta ptr
y devuelve un puntero a un objeto que tiene el tamaño especificado por size
. ptr
es el puntero a un bloque de memoria asignado previamente con malloc
, calloc
o realloc
(o un puntero nulo) para ser reasignado. Se conservan los máximos contenidos posibles de la memoria original. Si el nuevo tamaño es más grande, cualquier memoria adicional más allá del tamaño anterior no estará inicializada. Si el nuevo tamaño es más corto, el contenido de la parte contraída se pierde. Si ptr
es NULL, se asigna un nuevo bloque y la función devuelve un puntero a él.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(10 * sizeof *p);
if (NULL == p)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
p[0] = 42;
p[9] = 15;
/* Reallocate array to a larger size, storing the result into a
* temporary pointer in case realloc() fails. */
{
int *temporary = realloc(p, 1000000 * sizeof *temporary);
/* realloc() failed, the original allocation was not free'd yet. */
if (NULL == temporary)
{
perror("realloc() failed");
free(p); /* Clean up. */
return EXIT_FAILURE;
}
p = temporary;
}
/* From here on, array can be used with the new size it was
* realloc'ed to, until it is free'd. */
/* The values of p[0] to p[9] are preserved, so this will print:
42 15
*/
printf("%d %d\n", p[0], p[9]);
free(p);
return EXIT_SUCCESS;
}
El objeto reasignado puede o no tener la misma dirección que *p
. Por lo tanto, es importante capturar el valor de retorno de realloc
que contiene la nueva dirección si la llamada es exitosa.
Asegúrese de asignar el valor de retorno de realloc
a un temporary
lugar de a la p
original. realloc
devolverá el valor nulo en caso de que se realloc
un error, lo que sobrescribiría el puntero. Esto perdería sus datos y crearía una pérdida de memoria.
Arreglos multidimensionales de tamaño variable.
Desde C99, C tiene matrices de longitud variable, VLA, que matrices modelo con límites que solo se conocen en el momento de la inicialización. Si bien debe tener cuidado de no asignar VLA demasiado grande (podrían dañar su pila), usar los punteros a VLA y usarlos en el sizeof
expresiones está bien.
double sumAll(size_t n, size_t m, double A[n][m]) {
double ret = 0.0;
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
ret += A[i][j]
return ret;
}
int main(int argc, char *argv[argc+1]) {
size_t n = argc*10;
size_t m = argc*8;
double (*matrix)[m] = malloc(sizeof(double[n][m]));
// initialize matrix somehow
double res = sumAll(n, m, matrix);
printf("result is %g\n", res);
free(matrix);
}
Aquí la matrix
es un puntero a los elementos de tipo double[m]
, y la expresión sizeof
con double[n][m]
garantiza que contenga espacio para n
tales elementos.
Todo este espacio se asigna de forma contigua y, por lo tanto, puede ser desasignado por una sola llamada para free
.
La presencia de VLA en el lenguaje también afecta las posibles declaraciones de matrices y punteros en encabezados de función. Ahora, se permite una expresión entera general dentro de []
de los parámetros de la matriz. Para ambas funciones, las expresiones en []
utilizan parámetros que se han declarado anteriormente en la lista de parámetros. Para sumAll
estas son las longitudes que el código de usuario espera para la matriz. Como para todos los parámetros de la función de matriz en C, la dimensión más interna se reescribe en un tipo de puntero, por lo que esto es equivalente a la declaración
double sumAll(size_t n, size_t m, double (*A)[m]);
Es decir, n
no es realmente parte de la interfaz de la función, pero la información puede ser útil para la documentación y también puede ser utilizada por compiladores de verificación de límites para advertir sobre el acceso fuera de los límites.
De manera similar, para main
, la expresión argc+1
es la longitud mínima que el estándar C prescribe para el argumento argv
.
Tenga en cuenta que oficialmente el soporte de VLA es opcional en C11, pero no conocemos ningún compilador que implemente C11 y que no los tenga. Puedes probar con la macro __STDC_NO_VLA__
si es necesario.
realloc (ptr, 0) no es equivalente a free (ptr)
realloc
es conceptualmente equivalente a malloc + memcpy + free
en el otro puntero.
Si el tamaño del espacio solicitado es cero, el comportamiento de realloc
está definido por la implementación. Esto es similar para todas las funciones de asignación de memoria que reciben un parámetro de size
de valor 0
. Dichas funciones pueden, de hecho, devolver un puntero que no sea nulo, pero eso nunca debe ser referenciado.
Por lo tanto, realloc(ptr,0)
no es equivalente a free(ptr)
. Puede
- ser una implementación "perezosa" y simplemente devolver
ptr
-
free(ptr)
, asigne un elemento ficticio y devuelva ese -
free(ptr)
y devuelve0
- Solo devuelve
0
por fallo y no hagas nada más.
Por lo tanto, en particular, los dos últimos casos son indistinguibles por el código de la aplicación.
Esto significa que realloc(ptr,0)
puede que realmente no libere / desasigne la memoria, por lo que nunca debe usarse como un reemplazo free
.
Gestión de memoria definida por el usuario
malloc()
menudo llama a las funciones subyacentes del sistema operativo para obtener páginas de memoria. Pero la función no tiene nada de especial y puede implementarse en C directa declarando una matriz estática grande y asignándola (existe una ligera dificultad para garantizar la alineación correcta, en la práctica, la alineación con 8 bytes es casi siempre adecuada).
Para implementar un esquema simple, un bloque de control se almacena en la región de la memoria inmediatamente antes del puntero que se devolverá de la llamada. Esto significa que se puede implementar free()
restando del puntero devuelto y leyendo la información de control, que generalmente es el tamaño del bloque más alguna información que le permite volver a la lista libre, una lista vinculada de bloques no asignados.
Cuando el usuario solicita una asignación, se busca la lista gratuita hasta que se encuentra un bloque de tamaño idéntico o mayor a la cantidad solicitada y, si es necesario, se divide. Esto puede llevar a la fragmentación de la memoria si el usuario realiza continuamente muchas asignaciones y libera de tamaño impredecible y en intervalos impredecibles (no todos los programas reales se comportan así, el esquema simple suele ser adecuado para programas pequeños).
/* typical control block */
struct block
{
size_t size; /* size of block */
struct block *next; /* next block in free list */
struct block *prev; /* back pointer to previous block in memory */
void *padding; /* need 16 bytes to make multiple of 8 */
}
static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;
Muchos programas requieren grandes cantidades de asignaciones de objetos pequeños del mismo tamaño. Esto es muy fácil de implementar. Simplemente usa un bloque con el siguiente puntero. Así que si se requiere un bloque de 32 bytes:
union block
{
union block * next;
unsigned char payload[32];
}
static union block arena[100];
static union block * head;
void init(void)
{
int i;
for (i = 0; i < 100 - 1; i++)
arena[i].next = &arena[i + 1];
arena[i].next = 0; /* last one, null */
head = &block[0];
}
void *block_alloc()
{
void *answer = head;
if (answer)
head = head->next;
return answer;
}
void block_free(void *ptr)
{
union block *block = ptr;
block->next = head;
head - block;
}
Este esquema es extremadamente rápido y eficiente, y se puede hacer genérico con cierta pérdida de claridad.
alloca: asignar memoria en la pila
Advertencia: la alloca
solo se menciona aquí en aras de la integridad. Es completamente no portátil (no está cubierto por ninguna de las normas comunes) y tiene una serie de características potencialmente peligrosas que lo hacen inseguro para quienes no lo saben. El código C moderno debe reemplazarlo con matrices de longitud variable (VLA).
#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default
void foo(int size) {
char *data = alloca(size);
/*
function body;
*/
// data is automatically freed
}
Asigne memoria en el marco de pila de la persona que llama, el espacio al que hace referencia el puntero devuelto se libera automáticamente cuando finaliza la función de la persona que llama.
Si bien esta función es conveniente para la administración automática de la memoria, tenga en cuenta que solicitar una asignación grande puede provocar un desbordamiento de pila y que no puede utilizar free
memoria free
asignada con alloca
(lo que podría causar más problemas con el desbordamiento de pila).
Por esta razón, no se recomienda utilizar alloca
dentro de un bucle ni una función recursiva.
Y como la memoria está free
cuando se devuelve la función, no se puede devolver el puntero como resultado de la función ( el comportamiento sería indefinido ).
Resumen
- llamada idéntica a
malloc
- Se libera automáticamente al regresar de la función.
- incompatible con
free
funcionesfree
,realloc
( comportamiento indefinido ) - el puntero no se puede devolver como un resultado de función ( comportamiento indefinido )
- el tamaño de la asignación está limitado por el espacio de pila, que (en la mayoría de las máquinas) es mucho más pequeño que el espacio de almacenamiento disponible para uso de
malloc()
- evitar el uso de
alloca()
y VLA (matrices de longitud variable) en una sola función -
alloca()
no es tan portátil comomalloc()
et al.
Recomendación
- No use
alloca()
en código nuevo
Alternativa moderna.
void foo(int size) {
char data[size];
/*
function body;
*/
// data is automatically freed
}
Esto funciona donde alloca()
funciona, y funciona en lugares donde alloca()
no funciona (dentro de los bucles, por ejemplo). Supone una implementación C99 o una implementación C11 que no define __STDC_NO_VLA__
.