Aprenda sobre los objetos de datos llamados uniones en lenguaje C incorporado.

La diferencia entre estructura y unión en C embebido

En un artículo anterior de esta serie, discutimos que las estructuras en C incrustada nos permiten agrupar variables de diferentes tipos de datos y tratarlas como un único objeto de datos.

Además de las estructuras, el lenguaje C admite otra construcción de datos, llamada unión, que puede agrupar diferentes tipos de datos como un único objeto de datos. Este artículo proporcionará información básica sobre los sindicatos. Primero veremos un ejemplo introductorio de declarar una unión, luego examinaremos una aplicación importante de este objeto de datos.

Ejemplo introductorio

Declarar una unión es muy parecido a declarar una estructura. Solo necesitamos reemplazar la palabra clave "struct" por "union". Considere el siguiente código de ejemplo:

                    prueba sindical {
uint8_t c;
uint32_t i;
};
                  

Esto especifica una plantilla que tiene dos miembros: "c", que toma un byte, y "i", que ocupa cuatro bytes.

Ahora, podemos crear una variable de esta plantilla de unión:

                    prueba sindical u1;
                  

Usando el operador miembro (.), Podemos acceder a los miembros de la unión "u1". Por ejemplo, el siguiente código asigna 10 al segundo miembro de la unión anterior y copia el valor de "c" a la variable "m" (que debe ser de tipo uint8_t).

                    u1.i = 10;
m = u1.c;
                  

¿Cuánto espacio de memoria se asignará para almacenar la variable "u1"? Mientras que el tamaño de una estructura es al menos tan grande como la suma de los tamaños de sus miembros, el tamaño de una unión es igual al tamaño de su variable más grande. El espacio de memoria asignado a un sindicato se compartirá entre todos los miembros del sindicato. En el ejemplo anterior, el tamaño de "u1" es igual al tamaño de uint32_t, es decir, cuatro bytes. Este espacio de memoria se comparte entre “i” y “c”. Por lo tanto, la asignación de un valor a uno de estos dos miembros cambiará el valor del otro miembro.

Quizás se esté preguntando: "¿Cuál es el punto de usar el mismo espacio de memoria para almacenar múltiples variables? ¿Hay alguna aplicación para esta característica?" Exploraremos este problema en la siguiente sección.

¿Necesitamos espacio de memoria compartida?

Veamos un ejemplo en el que una unión puede ser un objeto de datos útil. Suponga que, como se muestra en la Figura 1 a continuación, hay dos dispositivos en su sistema que necesitan comunicarse entre sí.

Figura 1

El "Dispositivo A" debe enviar información de estado, velocidad y posición al "Dispositivo B". La información de estado consta de tres variables que indican la carga de la batería, el modo de funcionamiento y la temperatura ambiente. La posición está representada por dos variables que muestran las posiciones de los ejes x e y. Finalmente, la velocidad está representada por una sola variable. Suponga que el tamaño de estas variables es como se muestra en la siguiente tabla.

Nombre de la variable Tamaño (Byte) Explicación
poder 1 Bateria cargada
modo de operación 1 Modo de operación
temperatura 1 Temperatura
x_pos 2 Posición X
y_pos 2 Posición Y
vel 2 Velocidad

Si el "Dispositivo B" necesita constantemente tener cada parte de esta información, podemos almacenar todas estas variables en una estructura y enviar la estructura al "Dispositivo B". El tamaño de la estructura será al menos tan grande como la suma del tamaño de estas variables, es decir, nueve bytes.

Por lo tanto, cada vez que el "Dispositivo A" habla con el "Dispositivo B", necesita transferir un marco de datos de 9 bytes a través del enlace de comunicación entre los dos dispositivos. La Figura 2 muestra la estructura que utiliza el "Dispositivo A" para almacenar las variables y el marco de datos que necesita pasar por el enlace de comunicación.

Figura 2

Sin embargo, consideremos un escenario diferente en el que solo ocasionalmente necesitamos enviar la información de estado. Además, suponga que no es necesario tener información de posición y velocidad en un momento dado. En otras palabras, a veces solo enviamos posición, a veces solo enviamos velocidad y, a veces, solo enviamos información de estado. En esta situación, no parece una buena idea almacenar la información en una estructura de nueve bytes y transferirla a través del enlace de comunicación.

La información de estado puede ser representada por sólo tres bytes; Para la posición y la velocidad, solo necesitamos cuatro y dos bytes, respectivamente. Por lo tanto, el número máximo de bytes que debe enviar el "Dispositivo A" en una transferencia es de cuatro, y en consecuencia, solo necesitamos cuatro bytes de memoria para almacenar esta información. Este espacio de memoria de cuatro bytes se compartirá entre nuestros tres tipos de mensajes (consulte la Figura 3).

Además, tenga en cuenta que la longitud de la trama de datos que pasa a través del enlace de comunicación se reduce de nueve bytes a cuatro bytes.

figura 3

Para resumir, si nuestro programa tiene variables que son mutuamente excluyentes, podemos almacenarlas en un área compartida de memoria para conservar un valioso espacio en la memoria. Esto puede ser importante, especialmente en el contexto de sistemas integrados con memoria limitada. En tales casos, podemos usar uniones para crear el espacio de memoria compartido requerido.

El ejemplo anterior muestra que usar una unión para manejar variables mutuamente excluyentes también puede ayudarnos a conservar el ancho de banda de la comunicación. Conservar el ancho de banda de la comunicación es a veces incluso más importante que conservar la memoria.

Usando Uniones para Paquetes de Mensajes

Veamos cómo podemos usar una unión para almacenar las variables del ejemplo anterior. Teníamos tres tipos diferentes de mensajes: estado, posición y velocidad. Podemos crear una estructura para las variables de los mensajes de estado y posición (para que las variables de estos mensajes se agrupen y manipulen como un único objeto de datos).

Las siguientes estructuras sirven para este propósito:

                    estructura {
uint8_t potencia;
unit8_t op_mode;
uint8_t temp;
estado}

estructura {
uint16_t x_pos;
unit16_t y_pos;
} posición;
                  

Ahora, podemos poner estas estructuras junto con la variable "vel" en una unión:

                    Unión {
estructura {
uint8_t potencia;
unit8_t op_mode;
uint8_t temp;
estado}

estructura {
uint16_t x_pos;
unit16_t y_pos;
} posición;

                uint16_t vel;

} msg_union;
                  

El código anterior especifica una plantilla de unión y crea una variable de esta plantilla (llamada "msg_union"). Dentro de esta unión, hay dos estructuras ("estado" y "posición") y una variable de dos bytes ("vel"). El tamaño de esta unión será igual al tamaño de su miembro más grande, es decir, la estructura de "posición", que ocupa cuatro bytes de memoria. Este espacio de memoria se comparte entre las variables "estado", "posición" y "vel".

Cómo mantenerse al tanto del miembro activo de la Unión

Podemos usar el espacio de memoria compartida de la unión anterior para almacenar nuestras variables; sin embargo, queda una pregunta: ¿Cómo debe el receptor determinar qué tipo de mensaje se ha enviado? El receptor debe reconocer el tipo de mensaje para poder interpretar con éxito la información recibida. Por ejemplo, si enviamos un mensaje de "posición", los cuatro bytes de los datos recibidos son importantes, pero para un mensaje de "velocidad", solo se deben usar dos de los bytes recibidos.

Para resolver este problema, debemos asociar nuestra unión con otra variable, digamos "msg_type", que indica el tipo de mensaje (o el miembro de la unión que se escribió por última vez). Una unión emparejada con un valor discreto que indica que el miembro activo de la unión se llama "unión discriminada" o "unión etiquetada".

Con respecto al tipo de datos para la variable "msg_type", podemos usar el tipo de datos de enumeración del lenguaje C para crear constantes simbólicas. Sin embargo, usaremos un carácter para especificar el tipo de mensaje, solo para mantener las cosas lo más simples posible:

                    estructura {
uint8_t msg_type;
Unión {
estructura {
uint8_t potencia;
unit8_t op_mode;
uint8_t temp;
estado}

estructura {
uint16_t x_pos;
  unit16_t y_pos;
} posición;

                 uint16_t vel;

} msg_union;
} mensaje;
                  

Podemos considerar tres valores posibles para la variable "msg_type": "s" para un mensaje de "estado", "p" para un mensaje de "posición" y "v" para un mensaje de "velocidad". Ahora, podemos enviar la estructura del "mensaje" al "Dispositivo B" y usar el valor de la variable "msg_type" como indicador del tipo de mensaje. Por ejemplo, si el valor del "msg_type" recibido es "p", el "Dispositivo B" sabrá que el espacio de memoria compartida contiene dos variables de 2 bytes.

Tenga en cuenta que tendremos que agregar otro byte a la trama de datos enviada a través del enlace de comunicación porque necesitamos transferir la variable "msg_type". También tenga en cuenta que, con esta solución, el receptor no necesita saber de antemano qué tipo de mensaje está llegando.

La solución alternativa: asignación dinámica de memoria

Vimos que las uniones nos permiten declarar un área de memoria compartida para conservar tanto el espacio de memoria como el ancho de banda de comunicación. Sin embargo, existe otra forma de almacenar variables mutuamente excluyentes, como las del ejemplo anterior. Esta segunda solución utiliza la asignación de memoria dinámica para almacenar las variables de cada tipo de mensaje.

Nuevamente, necesitaremos tener una variable "msg_type" para especificar el tipo de mensaje tanto en el extremo del transmisor como en el del receptor del enlace de comunicación. Por ejemplo, si el "Dispositivo A" necesita enviar un mensaje de posición, establecerá "msg_type" en "p" y asignará cuatro bytes de espacio de memoria para almacenar las variables "x_pos" y "y_pos". El receptor verificará el valor de "msg_type" y, dependiendo de su valor, creará el espacio de memoria apropiado para almacenar e interpretar el marco de datos entrante.

El uso de la memoria dinámica puede ser más eficiente en términos de uso de la memoria porque estamos asignando el espacio suficiente para cada tipo de mensaje. Este no fue el caso con la solución basada en la unión. Allí, teníamos cuatro bytes de memoria compartida para almacenar los tres tipos de mensajes, aunque los mensajes de "estado" y "velocidad" solo requerían tres y dos bytes, respectivamente. Sin embargo, la asignación de memoria dinámica puede ser más lenta, y el programador debe incluir un código que libere la memoria asignada. Es por eso que los programadores generalmente prefieren usar la solución basada en la unión.

Siguiente: Aplicaciones de los sindicatos

Parece que el propósito original de las uniones era crear un área de memoria compartida para variables mutuamente excluyentes. Sin embargo, las uniones también se han utilizado ampliamente para extraer partes más pequeñas de datos de un objeto de datos más grande.

El siguiente artículo de esta serie se centrará en esta aplicación de los sindicatos, que puede ser particularmente importante en las aplicaciones integradas.

Para ver una lista completa de mis artículos, visite esta página.

Dejar respuesta

Please enter your comment!
Please enter your name here