La asignación de memoria sobre la marcha es la forma en que los programas C crecen y se reducen mientras se ejecutan. No conoce las necesidades exactas de su aplicación hasta que realmente se está ejecutando. La solución es el montón. Solicitas un bloqueo. El sistema operativo lo reserva. Lo usas. Luego lo devuelves con “gratis”. Este ciclo permite que otros programas reciclen esa misma RAM.
Considere este sencillo ejemplo. Es la interacción más básica con el montón que puedes conseguir.
Cómo funciona realmente Malloc
La función malloc no es mágica. Es un mecánico revisando el inventario. Cuando lo llamas, suceden tres cosas en secuencia.
- Comprueba la capacidad actual del montón. Mira el parámetro que pasaste, en este caso
sizeof(int), que es de 4 bytes. Pregunta si existe suficiente espacio contiguo. Si el montón está fragmentado o lleno, falla. - Si existe espacio, reserva ese bloque. El sistema lo marca como “tuyo”. Ninguna otra llamada a
mallocpuede tocar este fragmento. Previene colisiones accidentales entre asignaciones. - Devuelve la dirección de ese bloque. No te da los datos en sí. Te proporciona una variable de puntero. Esa variable contiene la ubicación. Ahora tienes acceso a un lugar específico en la RAM.
En nuestro ejemplo, p recibe esa dirección. La variable de puntero es solo un contenedor para una ubicación de memoria. El valor entero real vive en esa ubicación.
Por qué debes comprobar si hay NULL
Verá la marca if (p == 0) inmediatamente después de la asignación. Algunos principiantes se saltan esto. Es un error fatal.
Siempre verifique el puntero después de cualquier llamada a malloc para asegurarse de que sea válido.
El tamaño del montón fluctúa constantemente. Depende de qué otros procesos se estén ejecutando. Depende de cuánta memoria ellos hayan capturado. No hay garantía de que una llamada tenga éxito. Si malloc falla, devuelve cero. Cero es “NULO”. Si intenta eliminar la referencia a un puntero nulo, su programa falla. Duro.
Podrías escribir if (p == NULL) o if (!p). Significan lo mismo. Pero debes comprobarlo. Cada vez.
¿Qué pasa si olvidas liberar memoria?
Si su programa finaliza y no llamó “gratis”, el sistema operativo se limpia. Libera el espacio ejecutable. Limpia la pila. Reclama la memoria global. También recicla cualquier asignación de montón pendiente.
Entonces, ¿está bien perder memoria al final de un programa? Técnicamente, sí. El sistema operativo se encarga de la limpieza. No hay consecuencias a largo plazo para la máquina.
Pero es de mala educación. Y lo que es más importante, las pérdidas de memoria durante la ejecución son mortales. Si continúa asignando sin liberar, privará a su propio proceso. El montón se llena. Las nuevas asignaciones fracasan. El programa muere por asfixia, no por accidente.
Punteros frente a los valores a los que apuntan
Los consejos pueden resultar confusos. Son distintos de los valores a los que hacen referencia. Así es como se desarrolla esto en la práctica.
Primer escenario. Asignas un bloque. Le apuntas dos variables.
Salida:
10
20
¿Por qué? p y q son punteros diferentes. Pero q = p los hace apuntar al mismo bloque de memoria. Cambiar el valor a través de p lo cambia a q. Cambiarlo a través de q lo cambia por p. Son dos tiradores de una misma puerta.
Segundo escenario. Asigna dos bloques separados.
Salida:
20
Aquí, p y q apuntan a diferentes ubicaciones. *q contiene 20. *p = *q copia el valor 20 en la ubicación a la que apunta p. El puntero “p” en sí no se mueve. El valor en la dirección de p cambia.
La distinción importa. Se cambia la dirección almacenada en la variable. El otro cambia los datos en esa dirección. Confundir ambos conduce a errores lógicos que son más difíciles de depurar que los errores de sintaxis.


Punteros, memoria y caída libre
El resultado es 20. La línea 6 lo dice todo.
Podrías pensar que *p = *q y p = q hacen lo mismo. No lo hacen. Uno mueve datos. El otro mueve direcciones. El compilador le permite asignar *p = *q porque ambos punteros desreferenciados son números enteros. Es un intercambio directo de valores. ¿Pero p = q? Eso es diferente. Apunta “p” exactamente al mismo bloque de memoria que “q”. La dirección se mueve. Los datos se quedan.
Si los tipos no coinciden, el compilador lo bloquea. Un puntero a un número entero no puede apuntar a una cadena de caracteres. Los tipos deben alinearse.
Cuatro formas de inicializar
Una nueva variable de puntero es un pasivo hasta que se inicializa.
int *p; crea un puntero que no apunta a ningún lugar específico. Apunta a la basura. Desreferenciarlo es un error. Necesitas una ubicación conocida. Hay cuatro formas de llegar.
- malloc : esto asigna un bloque en el montón.
pahora tiene una dirección válida. El puntero se inicializa porque apunta a una porción de memoria reservada específica. - Asignación :
p = q;siqes válido,pse vuelve válido. Hereda la dirección de “q”. Siqes basura,ptambién es basura. - Dirección explícita : Puede apuntar
pdirectamente a una variable conocida.p = &i;apuntapal número enteroi. Simple. Directo. - Cero/NULL :
p = 0;op = NULL;. Esto establece la dirección en cero. No apunta a un bloque. Apunta a la ubicación nula.
¿Por qué usar cero? Es una bandera.
Puede verificar if (p == 0) para ver si el puntero es válido. El sistema lo reconoce. Si intenta eliminar la referencia a un puntero nulo, el programa falla. Duro.
p = 0; *pag = 5;
La segunda línea falla. p no apunta a nada. No puedes escribir sobre nada. Este comportamiento es fundamental para las listas enlazadas más adelante. Es una red de seguridad.
Liberando el montón
Asignar memoria no es suficiente. Tienes que devolverlo.
malloc ocupa espacio. free lo devuelve.
free(p) hace dos cosas. Primero, desreserva el bloque. El montón recupera esa memoria. Se puede reutilizar. En segundo lugar, “p” no se inicializa. No se vuelve seguro mágicamente. Debes reinicializar p antes de usarlo nuevamente.
El bloque ha desaparecido de su alcance. El puntero cuelga.
Este es el patrón estándar para manejar un único número entero en el montón:
Esto es principalmente una demostración. Pero muestra el ciclo de vida.
sizeof(int) devuelve el tamaño en bytes. En la mayoría de las máquinas, eso es 4. Puede codificar malloc(4). No. sizeof hace que el código sea portátil. Se lee mejor. Se adapta si la arquitectura cambia.
malloc devuelve un puntero genérico. Los compiladores a menudo advierten si no lo lanzas. (int *) convierte ese blob genérico en un puntero a un número entero. Coincide con “p”.
free(p) devuelve el bloque al montón.
Estructuras y precedencia de operadores
Las estructuras se comportan de la misma manera. Asigna un bloque para toda la estructura, no solo para los campos.
Mire (*p).i = 10;.
¿Por qué los paréntesis?
Porque . tiene mayor prioridad que *.
Sin paréntesis, *p.i significa *(p.i). Intenta acceder a “i” en el puntero “p” y luego elimina la referencia a eso. Eso no es lo que quieres. Primero desea eliminar la referencia a “p” y luego acceder a “i”.
Las reglas de precedencia son estrictas. * se une más estrechamente que . en este contexto. Los paréntesis obligan a que la desreferencia se produzca antes del acceso del miembro.
La mayoría de la gente odia escribir (*p).i repetidamente. Es detallado.
C tiene una taquigrafía.
p->i
Es exactamente lo mismo que (*p).i. Pero es más rápido escribir. Verás -> en todas partes del código C. Es la forma estándar de acceder a los miembros de la estructura mediante un puntero.
El montón es un recurso finito. Asigne con cuidado. Desasignar consistentemente. O observe cómo se le escapa la memoria.


























