Gestión de memoria en C. Parte II: Array, estructura, … 2


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.

Asignación de memoria para un array de 8 char
Asignación de memoria para un array de 8 char.
Posicionamiento de los elementos de un array.
Posicionamiento de los elementos de un array.
Asignación de memoria para elementos de tipo int. en un array
Asignación de memoria para elementos de tipo int.

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

Asignación de memoria en un array multidimensional.
Asignación de memoria en un array multidimensional.

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.

estructura y array

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.

Asignación de los campos de una estructura (miPrimeraEstructura) con padding entre un char y un float.

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.

Ordenación de una estructura para reducir padding y tamaño.

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.

Eliminación del padding entre campos mediante pragma pack.

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.

Estructura vs Union en memoria

Si tenemos un union declarado en memoria:

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

Modificación de uno de los campos de un union

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.

Declaración de un enum con valores asignados
Enumerador de tipo char por ASCII





Referencias:

  • Material de clase preparado para las asignaturas de Programación impartidas en los grados de UFV


Deja un comentario

2 ideas sobre “Gestión de memoria en C. Parte II: Array, estructura, …