sistema resumen educativo educacion definicion anacrónico anacronica c++ c data-structures unions

c++ - resumen - Unión: ¿anacronismo inútil o truco útil de la vieja escuela?



educacion anacronica definicion (11)

A menudo se utiliza en la especificación de los protocolos de transmisión de datos, donde le gustaría evitar perder espacio en sus estructuras de datos. Permite guardar el espacio de la memoria utilizando el mismo espacio para múltiples opciones mutuamente excluyentes.

Por ejemplo:

enum PacketType {Connect, Disconnect}; struct ConnectPacket {}; struct DisconnectPacket {}; struct Packet { // ... // various common data // ... enum PacketType type; union { ConnectPacket connect; DisconnectPacket disconnect; } payload; };

Las estructuras ConnectPacket y DisconnectPacket ocupan el mismo espacio, pero está bien porque un paquete no puede ser de ambos tipos al mismo tiempo. El valor enum se usa para determinar qué parte de la unión está en uso. El uso de la unión nos ha permitido evitar la duplicación de las partes comunes de la estructura de paquetes.

Hace poco encontré un excelente libro de estructuras de datos, " Data Structures Using C " (c) 1991, en una venta de libros de la biblioteca local por solo $ 2 . Como lo implica el título del libro, el libro cubre estructuras de datos que utilizan el lenguaje de programación C.

Obtuve el libro sabiendo que sería obsoleto, pero que probablemente contenga muchos temas avanzados de C que no encontraría en ningún otro lado.

Efectivamente en 5 minutos encontré algo que no sabía sobre C. Encontré una sección que hablaba de la palabra clave union y me di cuenta de que nunca la había usado, ni he visto ningún código que lo haga. Agradecí aprender algo interesante y rápidamente compré el libro.

Para aquellos de ustedes que no conocen lo que es una unión, el libro usa una buena metáfora para explicar:

Para comprender completamente el concepto de unión, es necesario examinar su implementación. Una estructura puede considerarse como un mapa de ruta para un área de memoria. Define cómo se debe interpretar la memoria. Una unión proporciona varios mapas de ruta diferentes para la misma área de memoria, y es responsabilidad del programador determinar qué hoja de ruta está en uso actual. En la práctica, el compilador asigna suficiente almacenamiento para contener al miembro más grande de la unión. Sin embargo, es el mapa de ruta el que determina cómo se debe interpretar ese almacenamiento.

Fácilmente podría llegar a situaciones artificiales o piratear donde usaría una Unión. (Pero no estoy interesado en situaciones artificiales o pirateos ...)

¿Ha utilizado o visto una implementación en la que el uso de Union resolvió el problema ** de forma más elegante ** que no utilizar un Sindicato?

Bonificación adicional si incluye una explicación rápida de por qué usar unión era mejor / más fácil que no usar una unión.


Considere el caso de acceder a bytes individuales dentro de una variable grande:

UInt32 x; x = 0x12345678; int byte_3 = x & 0x000000FF; // 0x78 int byte_2 = (x & 0x0000FF00) >> 8; // 0x56 int byte_1 = (x & 0x00FF0000) >> 16; // 0x34 int byte_0 = (x & 0xFF000000) >> 24; // 0x12

Esto puede ser mucho más elegante con una unión:

typedef union { UInt32 value; // 32 bits Byte byte[4]; // 4 * 8 bits } UInt32_Bytes; UInt32_Bytes x; x.value = 0x12345678; int byte_3 = x.byte[3]; // 0x78 int byte_2 = x.byte[2]; // 0x56 int byte_1 = x.byte[1]; // 0x34 int byte_0 = x.byte[0]; // 0x12

El uso de una unión significa que ya no tiene que usar máscaras de bits y operadores de desplazamiento para acceder a los bytes individuales. También hace que el acceso de bytes sea explícito.


De hecho, es una gran herramienta cuando escribes cosas como los controladores de dispositivos (una struct que deseas enviar al dispositivo que puede tener varios formatos similares pero diferentes) y necesitas una disposición de memoria precisa ...


Debe tener en cuenta que en C ++ no son una solución tan buena, ya que solo los tipos POD (datos antiguos simples) se pueden colocar en una unión. Si su clase tiene un constructor, destructor, contiene clases que tienen constructores y / o destructores (y aproximadamente un millón de otros errores), no puede ser miembro de una unión.


Es útil para configurar bits en, por ejemplo, registros en lugar de operaciones de desplazamiento / máscara:

typedef union { unsigned int as_int; // Assume this is 32-bits struct { unsigned int unused1 : 4; unsigned int foo : 4; unsigned int bar : 6; unsigned int unused2 : 2; unsigned int baz : 3; unsigned int unused3 : 1; unsigned int quux : 12; } field; } some_reg;

Nota: La forma en que ocurre el empaque depende de la máquina.

some_reg reg; reg.field.foo = 0xA; reg.field.baz = 0x5; write_some_register(some_address, reg.as_int);

Podría haber explotado alguna sintaxis en algún lugar allí, mi C está oxidado :)

EDITAR:

Por cierto, esto funciona de la manera opuesta también:

reg.as_int = read_some_register(some_address); if(reg.field.bar == BAR_ERROR1) { ...


Es una forma bastante buena de obtener los valores de bit IEEE de un flotante (asumiendo por supuesto que los flotantes son IEEE en su sistema). Cualquier cosa que implique emitir float * a int * arriesga tropezar con las estrictas reglas de aliasing. Esto no es solo teórico: altos niveles de optimización realmente romperán su código.

Técnicamente, la unión no se ocupa del problema. En la práctica, todos los compiladores conocidos (a) le permitirán escribir a un miembro de una unión y volver a leer otra, y (b) realizar la lectura después de realizar la escritura. GCC al menos es capaz de hacer circular la unión en un registro, convirtiendo todo en un no-operativo (suponiendo que los flotadores se almacenen en los registros para comenzar).


Hemos usado sindicatos en muchos códigos para el análisis de paquetes de red.

Union asigna el tamaño del elemento más grande. Crearía una unión con un elemento de almacenamiento intermedio con el tamaño máximo de mensaje, luego podrá acceder fácilmente a los valores del paquete.

Imagine que los datos "c123456" llegaron en línea y necesita analizar y acceder a los valores:

#include <iostream> using namespace std; struct msg { char header; union { char a[3]; char b[2]; char c[5]; char d[6]; char buf[10]; } data; } msg; int main() { struct msg m; memcpy(&m, "c123456", sizeof("c123456")); cout << "m.header: " << m.header << endl; cout << "m.data.d: " << string(m.data.d,sizeof(m.data.d)) << endl; cout << "m.data.b: " << string(m.data.b,sizeof(m.data.b)) << endl; switch (m.header) { case ''a'': cout << "a: " << string(m.data.a, sizeof(m.data.a)) << endl; break; case ''b'': cout << "b: " << string(m.data.b, sizeof(m.data.b)) << endl; break; case ''c'': cout << "c: " << string(m.data.c, sizeof(m.data.c)) << endl; break; default: break; } }

La salida se vería así:

m.header: c m.data.d: 123456 m.data.b: 12 c: 12345



Lo usé una vez para un tipo aproximado de polimorfismo de datos de una manera similar a respuesta . Tenía varios tipos diferentes de datos que quería usar potencialmente. Creé una unión de todos esos tipos y una estructura que contenía la unión y un código que definía qué tipo debía usarse.

union { data_type_1; data_type_2; data_type_3; } data_union; typedef struct _TAG_DATA_WRAPPED_ { data_union data; int data_type; //better an enum } WRAPPED_DATA; WRAPPED_DATA loads_of_data[1024];

Para responder a su pregunta sobre por qué esto es ventajoso:

Lo que esto le permite hacer es asignar fácilmente listas o matrices de diferentes tipos de datos y administrar su tipo de forma programática. El gran problema es, por supuesto, el espacio de almacenamiento, porque si los tipos tienen tamaños de almacenamiento muy diferentes puede perder mucho espacio.


Los UNION implementan algún tipo de polimorfismo en un mundo no OOP. Por lo general, tiene una parte que es común y, dependiendo de esa parte, usa el resto de los UNION. Por lo tanto, en casos en los que no tiene un lenguaje OOP y desea evitar una excesiva aritmética del puntero, los sindicatos pueden ser más elegantes en algunos casos.


Sé que esto se ha repetido, pero solo publicaré una muestra de código para ver cómo los sindicatos agregan elegancia y eficiencia al leer el tráfico de la red:

#pragma packed(1) struct header_t { uint16_t msg_id; uint16_t size; }; struct command_t { uint8_t cmd; }; struct position_t { uint32_t x; uint32_t y; uint32_t z; }; // ... Rest of the messages in an IDS struct message { header_t header; union { command_t command; position_t position; } body; }; #pragma packed(0) message read( int socket ) { message data; unsigned int readed = read( socket, &data, sizeof(header_t) ); // error checks... readed bytes smaller than header size and such readed = read( socket, &(data.body), data.header.size ); // error checks... }

En el fragmento anterior, puede realizar la lectura del mensaje en su lugar, y no necesita preocuparse por el tipo concreto de objeto recibido. Si no usó la unión, se quedaría leyendo el encabezado, extrayendo tanto el tamaño como el tipo, instanciando un objeto del tipo apropiado (ya sea en una jerarquía o para incluir dentro de una variante como boost :: any / boost :: variant) y realizar la segunda lectura en el espacio recién creado.

Utilizamos esta solución ampliamente para controlar simuladores (algunas empresas no aprecian las ''nuevas'' tecnologías como DDS o HLA y aún dependen de datos RAW / TCP brutos para sus simuladores). En la capa de red utilizamos uniones que se transforman en estructuras de datos internas (conversión de red a servidor, escalado de datos ...) antes de alimentarlo en las capas de aplicación. Como se mencionó anteriormente, debe tener cuidado con el relleno en todo momento.