¿Por qué, o cuándo, necesita asignar dinámicamente la memoria en C?
malloc dynamic-memory-allocation (3)
La asignación de memoria dinámica es un tema muy importante en la programación de C Sin embargo, no he podido encontrar una buena explicación de lo que esto nos permite hacer, o por qué es necesario.
¿No podemos simplemente declarar variables y estructuras y nunca tener que usar malloc ()?
Como nota al margen, ¿cuál es la diferencia entre:
ptr_one = (int *)malloc(sizeof(int));
y
int *ptr_one = malloc(sizeof(int));
Como nota al margen, ¿cuál es la diferencia entre:
ptr_one = (int *)malloc(sizeof(int))
eint *ptr_one = malloc(sizeof(int))
Ver this
En primer lugar, sé que esta es una pregunta ridícula, ya que la asignación de memoria dinámica es un tema muy importante en la programación de C. Sin embargo, no he podido encontrar una buena explicación de lo que esto nos permite hacer, o por qué es necesario.
El grupo de memoria (o más comúnmente el montón) es muy grande en comparación con la pila. Considere estos dos ejemplos de por qué es útil usar el conjunto de memoria sobre la pila:
1. ¿Qué sucede si define una matriz y desea que persista entre varios marcos de pila? Claro, puede declararlo como una variable global y se almacenará en la sección de datos globales de la memoria, sin embargo, esto se verá desordenado a medida que su programa se haga más y más grande. Alternativamente, puede almacenarlo en el grupo de memoria.
int *func( int k ) {
assert( k >= 1 );
int *ptr_block = malloc( sizeof( int ) * k );
if ( ptr_block == NULL ) exit( EXIT_FAILURE );
for ( int i = 0; i < k; i++ ) {
ptr_block[ i ] = i + 1;
}
return ptr_block; // Valid.
}
... Sin embargo, esto no funcionaría si definieras tu matriz en la pila. La razón es que, una vez que se ha extraído un marco de pila, todas las direcciones de memoria pueden ser utilizadas por otro marco de pila (y, por lo tanto, sobrescritas), mientras que el uso de la memoria de la agrupación de memoria persistirá hasta que el usuario (usted o cliente) free
d.
2. ¿Qué sucedería si quisiera implementar una matriz dinámica para manejar la lectura de una secuencia grande y arbitraria de números? No podría hacer esto definiendo su matriz en la pila, tendría que usar el grupo de memoria. Recuerde que es extremadamente común (y altamente recomendado a menos que necesite copiar explícitamente una estructura) pasar un puntero a una estructura, nunca la estructura en sí misma (ya que pueden ser bastante grandes). Considere esta pequeña implementación de una matriz dinámica:
struct dyn_array {
int *arr;
int len;
int cap;
};
typedef struct dyn_array *DynArray;
void insert_item( int const item, DynArray dyn_arr ) {
// Checks pre conditions.
assert( dyn_arr != NULL );
// Checks if the capacity is equal to the length. If so, double.
if ( dyn_arr->cap == dyn_arr->len ) {
dyn_arr->cap *= 2;
DynArray new_dyn_arr = malloc( sizeof( int ) * dyn_arr->cap ); // [oo]
// ... copy, switch pointers and free...
}
// ... insert, increase length, etc.
}
... en la línea [oo]
observe que si esto se definiera en la pila, una vez que se haga estallar este marco de pila, ya no se asignarán todas las direcciones de memoria para la matriz. Es decir, otro marco de pila (probablemente el siguiente) usará esas direcciones de memoria (o algún subconjunto de la misma).
Observación: de mi fragmento de código, ptr_block
se almacena en la pila: por lo tanto, &ptr_block
es una dirección de pila, sin embargo, el valor de ptr_block
está en algún lugar de la agrupación de memoria.
La asignación dinámica es necesaria cuando no conoce los requisitos de peor caso para la memoria. Entonces, es imposible asignar estáticamente la memoria necesaria, porque no sabes cuánto necesitarás.
Incluso si conoce los requisitos del caso más desfavorable, puede ser conveniente utilizar la asignación de memoria dinámica. Permite que la memoria del sistema sea utilizada de manera más eficiente por múltiples procesos. Todos los procesos podrían comprometer de forma estática sus requisitos de memoria del peor caso, pero eso pone un límite a la cantidad de procesos en ejecución que pueden existir en el sistema. Si nunca es el caso que todos los procesos utilicen el peor de los casos al mismo tiempo, entonces la memoria del sistema se ejecuta constantemente de manera subutilizada, lo que es un desperdicio de recursos.
En cuanto a su pregunta complementaria, no debe emitir el resultado de una llamada a malloc()
en C. Puede ocultar el error de una declaración faltante (se permitieron las declaraciones implícitas antes de C.99), y da como resultado un comportamiento indefinido. Siempre prefiere tomar el resultado de malloc()
sin un yeso. Se declara que malloc()
devuelve void *
, y en C, siempre se permite una conversión entre void *
y otro tipo de puntero (calificadores de tipo módulo como const
).
Necesitas usar memoria dinámica cuando:
- No puede determinar la cantidad máxima de memoria para usar en tiempo de compilación;
- Quieres asignar un objeto muy grande;
- Desea construir estructuras de datos (contenedores) sin un tamaño superior fijo;
No siempre sabe cuánta memoria necesitará reservar a la hora de compilar. Imagine procesar un archivo de datos (una serie temporal de temperaturas, por ejemplo), donde el número de registros en el archivo no es fijo. Puede tener tan solo 10 registros o hasta 100000. Si desea leer todos los datos en la memoria para procesarlos, no sabrá cuánta memoria debe asignar hasta que lea el archivo. Si el archivo está estructurado de modo que el primer valor sea el número de registros, podría hacer algo como esto:
size_t recs = 0;
double *temps = NULL;
FILE *fp = fopen ( filename, "r" );
if ( fp )
{
if ( fscanf( fp, "%zu", &recs ) == 1 )
{
temps = malloc( sizeof *temps * recs );
if ( temps )
{
// read contents of file into temps
}
}
}
A veces necesitas asignar un objeto muy grande, algo como
int ginormous[1000][1000][1000];
Suponiendo un entero de 4 bytes, esta matriz requerirá 4 GB. Desafortunadamente, los marcos de pila (donde las variables locales se mantienen en la mayoría de las arquitecturas) tienden a ser mucho más pequeñas que eso, por lo que tratar de asignar tanta memoria puede llevar a un error en tiempo de ejecución (y normalmente lo hace). La agrupación de memoria dinámica (también conocida como el montón) es generalmente mucho más grande que la pila, y mucho menos en cualquier marco de pila. así que para algo tan desagradable tendría que escribir algo como
int (*ginormous)[1000][1000] = malloc( sizeof *ginormous * 1000 );
Todavía es posible que una solicitud como esa falle; Si su montón está suficientemente comprometido, es posible que no tenga un solo bloque contiguo lo suficientemente grande como para atender la solicitud. Si es necesario, podría hacer una asignación por partes; las filas no serán necesariamente adyacentes en la memoria, pero es más probable que puedas tomar toda la memoria que necesitas:
int ***ginormous = malloc( sizeof *ginormous * 1000 );
if ( ginormous )
{
for ( size_t i = 0; i < 1000; i++ )
{
ginormous[i] = malloc( sizeof *ginormous[i] * 1000 );
if ( ginormous[i] )
{
ginormous[i][j] = malloc ( sizeof *ginormous[i][j] * 1000 );
if ( ginormous[i][j] )
{
// initialize ginormous[i][j][k]
}
}
}
}
Y finalmente, la memoria dinámica le permite crear contenedores que pueden crecer y reducirse a medida que agrega o elimina datos, como listas, árboles, colas, etc. Incluso podría crear su propio tipo de datos "de cadena" real que puede crecer a medida que se anexa caracteres a ella (similar al tipo de string
en C ++).