Existen varios lenguajes de alto nivel en programación. Suelen clasificarse por características como la sintaxis, el tipado, los paradigmas de programación, abstracción o si son compilados o interpretados. Pero posiblemente la característica más diferenciadora del lenguaje C es la capacidad de trabajar con la memoria.
Los lenguajes de alto nivel están más próximos al lenguaje de los humanos que al de las máquinas. Suelen tratar un nivel de abstracción alto que solo maneja referencias y dejan toda la gestión a los compiladores o interpretes. En cambio C permite a los programadores bajar más en el nivel de abstracción y gestionar la memoria.
Esta capacidad ofrece más rapidez y versatilidad a los programas. Pero también incrementa exponencialmente las fuentes de error y los memory leaks que pueden romper las ejecuciones. Por eso es muy importante conocer como gestiona la memoria la programación en C. Tanto la estática como la dinámica. Ya que para optimizar el rendimiento de los programas puede suponer que estos sean viables en sistemas de pocos recursos como placas embebidas.
Virtualización de la memoria
Lo primero que hay que definir para entender la gestión de memoria es que significa la memoria de una computadora. Las memorias físicas de las computadoras, ya sea un disco de almacenamiento o una memoria volátil (RAM), son contenedores de bits. Estos bits son el equivalente a estados binarios que se representan como 0 y 1. Se «almacenan (o agrupan)» en paquetes de 8 bits (byte) y suelen referenciarse en sistema hexadecimal. Tanto en posición como en valor.
Una memoria física suele representarse de forma virtual hexadecimal de la siguiente manera:

Cada posición de memoria tiene una dirección asociada, también definida en hexadecimal, que localiza un byte exacto dentro de una memoria. En la imagen anterior la dirección 0x0044C3315 tiene un valor 0x15. Esta representación es para todos los sistemas y lenguajes, no solo para la gestión de memoria en C.
Mapeado de la memoria. Asignación de memoria estática.
Los sistemas operativos, OS, de las computadoras utilizan la memoria para sus operaciones. Ya sea una memoria volátil o algo más duradero, los valores almacenados en estas deben ser reconocidos por el sistema. Si una variable se guarda en memoria, cuando se quiera acceder a su valor, el sistema debe saber donde lo ha guardado.
Se suele decir que los sistemas hace un mapa (mapear o mapping) de la memoria. Gestionando esta mediante identificadores y gestores de asignación. De esta manera un espacio de memoria asignado a un programa no será asignado a otro de forma que no se corrompan los valores guardados.
Cuando se declara una variable, estructura, función, etc. el gestor de memoria busca en el mapa de memoria desasignada un hueco de tamaño igual o superior al que se declara y se asigna al nuevo identificador declarado.
El lenguaje C es un lenguaje tipado. Esto quiere decir que toda variable debe tener asociado un tipo de dato de los definidos en las librerías del lenguaje. Cada tipo de dato significa un tamaño en memoria y un rango de valores. En el caso de C los tipos básicos dependiendo de la arquitectura del sistema son:

Por lo que al declara una variable «numero» de tipo entero que ocupa 4 bytes, el OS y su gestor buscarán un espacio mayor o igual a 4 bytes desasignados y se asociaran al identificador número. De esta forma todo valor guardado en número o leído de su posición en memoria queda asociado a la variable hasta que termine la ejecución del programa y se desasigne.

Existe una particularidad al «leer» y «escribir» un valor en memoria entre algunos tipos de hardware. En los inicio de la computación. algunos fabricantes optaros por leer los datos hexadecimales de forma «natural». Como en la escritura común. Lo que implicaba empezar a asignar los bytes de derecha a izquierda. Pero otros fabricantes optaron por el sistema de izquierda a derecha. A estos dos sistemas se les denominó big y little endian. Y afectan a la conversión de valores en los tipos de más de 1 byte de longitud.
Por lo que cuando se guarda un valore en un memoria designada a una variable, su representación hexadecimal dependerá del endianness que esté definido en el hardware del sistema.

De esta forma, dada una dirección de memoria, se pueden obtener diferentes resultados dependiendo del tipo de dato que se desee manejar y del endianness del sistema.

Y agrupando paquetes del tamaño de los diferentes tipos definidos:


También puede trabajarse con agrupación de tipos de datos como los arrays o estructuras. En esos casos el OS tratará de asignar bloques de memoria que permitan que los datos y campos queden asignados correlativamente para aprovechar la capacidad de la programación en C de trabajar directamente con las direcciones de memoria sin gestores intermedios.
Otra de las ventajas de la gestión directa de memoria en C es la del tratamiento de los denominados buffer. Estos son cadenas de bytes de tamaño máximo que pueden gestionarse mediante reasignaciones o cast como tipos de datos diferentes según convenga. Pero de los que solo se conoce el puntero o la dirección del primer byte y la longitud máxima. Minimizado el peso en bytes de la gestión de ese espacio de memoria.
Este tipo de gestión de memoria está muy relacionada con la memoria dinámica de la programación en C.
Referencias:
- Material de clase preparado para las asignaturas de Programación impartidas en los grados de UFV

Pingback: Gestión de memoria en C. Parte II: Array, estructura, ... - Fisicotrónica
Pingback: Gestión de memoria en C. Parte III: Puntero. - Fisicotrónica