En el anterior artículo de esta serie comentamos como se virtualizan y gestionan las memorias internas de los sistemas informáticos. Mediante un mapeado de direcciones en formato hexadecimal y un control de asignación de bytes.
Los tipos definidos en C ocupan un espacio predeterminado en la memoria. Se dice que este tipo de asignaciones se denomina memoria estática ya que se declara una variable en el programa a la que se le asigna una dirección de memoria y un tipo (tamaño). Esta asignación es única y referenciar a la variable siempre referenciará al mismo espacio de memoria «estático» mientras la variable exista.
Array.
Las variables son elementos individuales. En caso de querer declarar colecciones de elementos de un mismo tipo, debemos declarar un array. Estos arrays son agrupaciones de elementos iguales a los que se les asigna una posición enumerada desde el 0 al N-1. Siendo N el número de elementos. El gestor de memoria del OS asignará una porción de memoria de un tamaño igual o mayor al número de elementos por el tamaño asignado al tipo declarado.
Si se declara un array de 8 valores tipo char, se asignará una memoria de 8 posiciones * 1 byte. Mientras que si se declaras 5 enteros será de 5 posiciones * 4 bytes.



Pueden encadenarse arrays declarando más dimensiones. Es como definir un array nuevo dentro de las posiciones de la dimensión superior.

Estructura
Tanto un array como una estructura es una colección de variables. La diferencia principal es que mientras que el array encapsula elementos de un mismo tipo, una estructura encapsula elementos diferentes. Estos elementos se denominan campos. Y cada campo se asocia a un identificador propio e individual dentro de la estructura.
Otra de las diferencias es que la estructura debe definirse antes de ser declarada. Principalmente en la parte de la cabecera o header del código. Y se asigna un nombre para diferenciar la estructura del resto de estructuras y tipos definidos en el programa y las bibliotecas incluidas.
Una estructura puede contener otras estructuras anidadas o arrays de elementos. También puede definirse un array de tipo estructura. De esta forma pueden declararse estructuras complejas para almacenar datos relacionados. El ejemplo más usado es el de una biblioteca. En la que se mezclan estanterías, secciones, tipos de libros, autores, colecciones, revistas, etc.

El tamaño de una estructura es la suma de los tamaños de los tipos de los campos declarados en su interior. Pero agrupados de 4 bytes en 4 bytes sin partir los tipos básicos. Por eso, en el caso de la imagen superior, la primera estructura ocupa 12 y no 9 bytes. El espacio de memoria que queda sin definir entre campos asignados se denomina padding o relleno.

La ordenación de los campos dentro de una estructura afecta al tamaño de esta en memoria. Cuando un programa es pequeño y el OS tiene recursos suficientes, esta ordenación no implicará problemas. Pero si los recursos son pequeños, optimizar los tamaños de las estructuras puede ser vital. Y eliminar los padding que se puedan generar para no añadir memoria descontrolada a la ejecución puede evitar multitud de errores. Sobre todo cuando se trabaja con punteros a memoria, buffers y cast de estructuras.

Una opción para reducir este problema en C es utilizar la directiva pragma pack. Con esta se pide al gestor de memoria que rompa los bloques por tipo de datos y que asigne los bytes de forma correlativa. De forma que los «agujeros» queden al final de la estructura. Puede que no se reduzca el tamaño, ya que este siempre se define como múltiplo de 4 bytes si no es un char. Pero podremos evitar trabajar con memoria descontrolada.

Union
El union es un tipo especial de estructura. Solo puede tener declarado un campo de cada tipo de dato. Y todos ocupan el mismo espacio de memoria que será el del tipo de mayor tamaño. Una vez asignado uno de los campos del union, todos los demás quedan alterados. Por lo que si asignamos un valor char, los int, float o double de la estructura también se modificarán.

Si tenemos un union declarado en memoria:

Y modificamos el valor del campo «caracter», los otros campos también cambian su valor.

Enumeradores
Los enumeradores son colecciones de valores correlativos a los que se les asigna un identificador más fácil de usar, modificar e identificar. Se dice que son valores correlativos, ya que un identificador tendrá, si no se indica otra cosa, un valor igual al del identificador superior más una unidad.
Este tipo de colecciones suele ser muy útil para listar objetos o declarar listas de códigos. También están muy relacionados con los casos del condicional switch/case. En C están definidos como enteros. Aunque puede trabajarse con char utilizando el código ASCII. Y son muy útiles para simplificar el código y no utilizar número mágicos.


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 I: Memoria estática y tipos. - Fisicotrónica
Pingback: Gestión de memoria en C. Parte III: Puntero. - Fisicotrónica