En esta sección veremos una introducción general a los distintos tipos de "memorias" que juegan a la hora de ejecutar un programa.
Nuestros programas constan en primer instancia del código fuente que escribimos. Luego, en lenguajes compilados, el compilador es el que "traduce" ese programa a otro lenguaje entendible para la computadora (sistema operativo), que se suele llamar "binario" o "ejecutable".
Ese programa binario es el que realmente se ejecuta.
Entonces, nos imaginaremos que al momento de ejecutar nuestro programa, él mismo tiene que ser cargado a memoria. Ya que la memoria representa a los programas "activos".
Acá ya aparece nuestro primer tipo de memoria: la Memoria de Código. (ojo no se refiere al código fuente, si no al ejecutable).
Acá tenemos un ejemplo de un hola mundo bien simple:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Hola Programacion 2 !");
return 0;
}
Eventualmente esta función se traduce a un objeto binario junto a sus instrucciones.
Ahora modificamos un poquito el ejemplo:
#include <stdio.h>
#include <stdlib.h>
static char *saludo = "Hola Programacion 2 !";
int main() {
printf(saludo);
return 0;
}
Acá tenemos el saludo en una variable global static.
Las variables static tienen la particularidad de que "viven" durante toda la ejecución del programa. Es decir, que se les reserva memoria durante el inicio del programa, y solo se "desocupa" esa memoria, al terminar el programa.
Para este tipo de variables es que existe otro tipo de memoria, que llamaremos: Memoria de Datos Estáticos.
Cambiemos el ejemplo nuevamente para soportar varios saludos, introduciendo una función "saludar".
#include <stdio.h>
#include <stdlib.h>
void saludar(int sufijo) {
printf("Hola Programacion %i !\n", sufijo);
}
int main() {
saludar(1);
saludar(2);
saludar(3);
return 0;
}
Acá vemos otro tipo de variable, los parámetros de funciones.
Cada función tiene define su contexto. Este contexto tiene variables. Y estas variables "viven" solo durante la ejecución de la función/procedimiento. Es decir que "nacen" al ingresar el llamado a la función y mueren al retornar el control a la función que la llamó (con return o implícitamente).
Como sabemos también, un programa puede llamar dinámicamente a una función, y ésta a su vez llamar a otra, y otra, y otra.
Esto se dá en forma dinámica. Entonces el lenguaje tiene un mecanismo de gestión de memoria automática para estos casos. Internamente se maneja con una Pila.
En nuestro ejemplo sucedería algo así:
Por último es común que los programas necesiten "recordar" cosas en forma dinámica.
Continuando con nuestro ejemplo simple de los saludos, podríamos pensar que el usuario va ingresando los saludos por consola, o bien el programa los lee desde un archivo. Es evidente que el programa no "sabe" de antemano, ni puede saber, cuántos saludos va a leer. Porque justamente queremos que sea genérico y funcione para N saludos. En estos casos, el programa necesita poder reservar memoria durante su ejecución (a diferencia de la memoria de datos estáticos que se reserva al iniciar).
Para esto existe una sección especial llamada Memoria Heap. Que es en general la parte que más se va a utilizar, y va a ir creciendo y decreciendo. El lenguaje C provée funciones para manejar esta memoria desde el mismo programa. Es decir para poder manipular memoria dinámicamente.
Esto es a lo que generalmente se le llama Memoria Dinámica, tema de nuestra unidad.
Existen los siguientes tipos de memoria dado un programa:
(imagen tomada de aquí)