uniones lenguaje funciones estructuras ejercicios ejemplo con arreglo anidadas c unions

lenguaje - typedef struct en c



¿Por qué necesitamos las uniones C? (18)

¿Cuándo deben usarse los sindicatos? ¿Por qué los necesitamos?


¿Qué pasa con VARIANT que se utiliza en las interfaces COM? Tiene dos campos: "tipo" y una unión que tiene un valor real que se trata según el campo "tipo".


Aquí hay un ejemplo de una unión de mi propio código base (de memoria y parafraseado, por lo que puede no ser exacto). Se usó para almacenar elementos del lenguaje en un intérprete que construí. Por ejemplo, el siguiente código:

set a to b times 7.

consta de los siguientes elementos de lenguaje:

  • símbolo [set]
  • variable [a]
  • símbolo [a]
  • variable [b]
  • símbolo [tiempos]
  • constante [7]
  • símbolo[.]

Los elementos del lenguaje fueron definidos como valores '' #define '' por lo tanto:

#define ELEM_SYM_SET 0 #define ELEM_SYM_TO 1 #define ELEM_SYM_TIMES 2 #define ELEM_SYM_FULLSTOP 3 #define ELEM_VARIABLE 100 #define ELEM_CONSTANT 101

y la siguiente estructura fue utilizada para almacenar cada elemento:

typedef struct { int typ; union { char *str; int val; } } tElem;

luego, el tamaño de cada elemento era el tamaño de la unión máxima (4 bytes para el tipo y 4 bytes para la unión, aunque esos son valores típicos, los tamaños reales se incluyen en la implementación).

Para crear un elemento "conjunto", usaría:

tElem e; e.typ = ELEM_SYM_SET;

Para crear un elemento "variable [b]", usaría:

tElem e; e.typ = ELEM_VARIABLE; e.str = strdup ("b"); // make sure you free this later

Para crear un elemento "constante [7]", usaría:

tElem e; e.typ = ELEM_CONSTANT; e.val = 7;

y podría expandirlo fácilmente para incluir flotadores ( float flt ) o racionales ( struct ratnl {int num; int denom;} ) y otros tipos.

La premisa básica es que str y val no son contiguos en la memoria, en realidad se superponen, por lo que es una forma de obtener una vista diferente en el mismo bloque de memoria, que se ilustra aquí, donde la estructura se basa en la ubicación de memoria 0x1010 y los enteros y Los punteros son ambos 4 bytes:

+-----------+ 0x1010 | | 0x1011 | typ | 0x1012 | | 0x1013 | | +-----+-----+ 0x1014 | | | 0x1015 | str | val | 0x1016 | | | 0x1017 | | | +-----+-----+

Si estuviera solo en una estructura, se vería así:

+-------+ 0x1010 | | 0x1011 | typ | 0x1012 | | 0x1013 | | +-------+ 0x1014 | | 0x1015 | str | 0x1016 | | 0x1017 | | +-------+ 0x1018 | | 0x1019 | val | 0x101A | | 0x101B | | +-------+


En la escuela, usaba uniones como esta:

typedef union { unsigned char color[4]; int new_color; } u_color;

Lo usé para manejar los colores con mayor facilidad, en lugar de usar los operadores >> y <<, simplemente tuve que pasar por el índice diferente de mi matriz de caracteres.


En las primeras versiones de C, todas las declaraciones de estructura compartirían un conjunto común de campos. Dado:

struct x {int x_mode; int q; float x_f}; struct y {int y_mode; int q; int y_l}; struct z {int z_mode; char name[20];};

un compilador produciría esencialmente una tabla de tamaños de estructuras (y posiblemente alineaciones), y una tabla separada de nombres, tipos y compensaciones de los miembros de las estructuras. El compilador no hizo un seguimiento de qué miembros pertenecían a qué estructuras, y permitiría que dos estructuras tuvieran un miembro con el mismo nombre solo si el tipo y el desplazamiento coincidían (como con el miembro q de struct x y struct y ). Si p era un puntero a cualquier tipo de estructura, p-> q agregaría el desplazamiento de "q" al puntero p y buscaría un "int" de la dirección resultante.

Dada la semántica anterior, fue posible escribir una función que podría realizar algunas operaciones útiles en múltiples tipos de estructura de manera indistinta, siempre que todos los campos utilizados por la función se alinearan con campos útiles dentro de las estructuras en cuestión. Esta fue una característica útil, y cambiar C para validar los miembros utilizados para el acceso a la estructura en comparación con los tipos de las estructuras en cuestión habría significado perderlo en ausencia de un medio para tener una estructura que pueda contener múltiples campos con nombre en la misma dirección. Agregar tipos de "unión" a C ayudó a llenar esa brecha (aunque no, IMHO, como debería haber sido).

Una parte esencial de la capacidad de los sindicatos para llenar ese vacío era el hecho de que un puntero a un miembro del sindicato se podría convertir en un puntero a cualquier sindicato que contenga ese miembro, y un puntero a cualquier unión se podría convertir en un puntero a cualquier miembro. Si bien el Estándar C89 no dijo expresamente que emitir un T* directamente a un U* era equivalente a un puntero a cualquier tipo de unión que contenga tanto T como U , y luego emitirlo a U* , no hay un comportamiento definido del la última secuencia de conversión se vería afectada por el tipo de unión utilizado, y el Estándar no especificó ninguna semántica contraria para una conversión directa de T a U Además, en los casos en que una función recibió un puntero de origen desconocido, el comportamiento de escribir un objeto a través de T* , convertir el T* en una U* y luego leer el objeto a través de U* sería equivalente a escribir una unión a través del miembro del tipo T y la lectura como tipo U , que se definiría de manera estándar en unos pocos casos (por ejemplo, al acceder a miembros de Common Initial Sequence) y definida por la implementación (en lugar de indefinida) para el resto. Si bien era raro que los programas explotaran las garantías CIS con objetos reales de tipo de unión, era mucho más común explotar el hecho de que los punteros a objetos de origen desconocido tenían que comportarse como punteros a miembros de la unión y tenían las garantías de comportamiento asociadas con ellos.


Es difícil pensar en una ocasión específica en la que necesitaría este tipo de estructura flexible, tal vez en un protocolo de mensajes en el que estaría enviando diferentes tamaños de mensajes, pero incluso entonces probablemente haya alternativas mejores y más fáciles de programar.

Los sindicatos son un poco como los tipos de variantes en otros idiomas: solo pueden contener una cosa a la vez, pero esa cosa podría ser un int, un flotador, etc., dependiendo de cómo se declare.

Por ejemplo:

typedef union MyUnion MYUNION; union MyUnion { int MyInt; float MyFloat; };

MyUnion solo contendrá un int O un flotante, según lo que haya configurado más recientemente . Así que haciendo esto:

MYUNION u; u.MyInt = 10;

u ahora tiene un int igual a 10;

u.MyFloat = 1.0;

u ahora tiene un flotador igual a 1.0. Ya no tiene un int. Obviamente ahora si intentas hacer printf ("MyInt =% d", u.MyInt); entonces es probable que obtenga un error, aunque no estoy seguro del comportamiento específico.

El tamaño de la unión es dictado por el tamaño de su campo más grande, en este caso el flotador.


La programación del sistema de bajo nivel es un ejemplo razonable.

IIRC, he usado uniones para desglosar registros de hardware en los bits de componentes. Por lo tanto, puede acceder a un registro de 8 bits (como era, en el día en que hice esto ;-) en los bits de componente.

(Olvidé la sintaxis exacta pero ...) Esta estructura permitiría acceder a un registro de control como control_byte o mediante los bits individuales. Sería importante asegurar que los bits se asignen a los bits de registro correctos para una endianidad determinada.

typedef union { unsigned char control_byte; struct { unsigned int nibble : 4; unsigned int nmi : 1; unsigned int enabled : 1; unsigned int fired : 1; unsigned int control : 1; }; } ControlRegister;


Lo he visto en un par de bibliotecas como un reemplazo para la herencia orientada a objetos.

P.ej

Connection / | / Network USB VirtualConnection

Si quieres que la "clase" de Conexión sea una de las anteriores, puedes escribir algo como:

struct Connection { int type; union { struct Network network; struct USB usb; struct Virtual virtual; } };

Ejemplo de uso en libinfinity: http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74


Los sindicatos permiten que los miembros de datos que son mutuamente exclusivos compartan la misma memoria. Esto es muy importante cuando la memoria es más escasa, como en los sistemas integrados.

En el siguiente ejemplo:

union { int a; int b; int c; } myUnion;

Esta unión ocupará el espacio de un solo int, en lugar de 3 valores int separados. Si el usuario establece el valor de a , y luego establece el valor de b , se sobrescribirá el valor de a, ya que ambos comparten la misma ubicación de memoria.


Los sindicatos se utilizan a menudo para convertir entre las representaciones binarias de enteros y flotadores:

union { int i; float f; } u; // Convert floating-point bits to integer: u.f = 3.14159f; printf("As integer: %08x/n", u.i);

Aunque este es un comportamiento técnicamente indefinido según el estándar C (se supone que solo debes leer el campo que se escribió más recientemente), actuará de manera bien definida en prácticamente cualquier compilador.

En ocasiones, los sindicatos también se usan para implementar el pseudopolimorfismo en C, al asignar a la estructura una etiqueta que indica qué tipo de objeto contiene y luego unir los tipos posibles:

enum Type { INTS, FLOATS, DOUBLE }; struct S { Type s_type; union { int s_ints[2]; float s_floats[2]; double s_double; }; }; void do_something(struct S *s) { switch(s->s_type) { case INTS: // do something with s->s_ints break; case FLOATS: // do something with s->s_floats break; case DOUBLE: // do something with s->s_double break; } }

Esto permite que el tamaño de la struct S sea ​​solo de 12 bytes, en lugar de 28.


Los sindicatos se utilizan cuando desea modelar estructuras definidas por hardware, dispositivos o protocolos de red, o cuando está creando una gran cantidad de objetos y desea ahorrar espacio. Sin embargo, realmente no los necesita el 95% del tiempo, quédese con el código fácil de depurar.


Los sindicatos son geniales Un uso inteligente de los sindicatos que he visto es usarlos al definir un evento. Por ejemplo, puede decidir que un evento es de 32 bits.

Ahora, dentro de esos 32 bits, le gustaría designar los primeros 8 bits como un identificador del remitente del evento ... A veces trata el evento como un todo, a veces lo analiza y compara sus componentes. Los sindicatos te dan la flexibilidad para hacer ambas cosas.

union Event { unsigned long eventCode; unsigned char eventParts[4]; };


Los sindicatos son particularmente útiles en la programación integrada o en situaciones donde se necesita acceso directo al hardware / memoria. Aquí hay un ejemplo trivial:

typedef union { struct { unsigned char byte1; unsigned char byte2; unsigned char byte3; unsigned char byte4; } bytes; unsigned int dword; } HW_Register; HW_Register reg;

A continuación, puede acceder al registro de la siguiente manera:

reg.dword = 0x12345678; reg.bytes.byte3 = 4;

La endianness (orden de bytes) y la arquitectura del procesador son, por supuesto, importantes.

Otra característica útil es el modificador de bits:

typedef union { struct { unsigned char b1:1; unsigned char b2:1; unsigned char b3:1; unsigned char b4:1; unsigned char reserved:4; } bits; unsigned char byte; } HW_RegisterB; HW_RegisterB reg;

Con este código puede acceder directamente a un solo bit en la dirección de registro / memoria:

x = reg.bits.b2;


Muchas de estas respuestas tienen que ver con el casting de un tipo a otro. Aprovecho al máximo el uso de uniones con los mismos tipos solo más (es decir, al analizar un flujo de datos en serie). Permiten que el análisis / construcción de un paquete enmarcado se vuelva trivial.

typedef union { UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for // the entire set of fields (including the payload) struct { UINT8 size; UINT8 cmd; UINT8 payload[PAYLOAD_SIZE]; UINT8 crc; } fields; }PACKET_T; // This should be called every time a new byte of data is ready // and point to the packet''s buffer: // packet_builder(packet.buffer, new_data); void packet_builder(UINT8* buffer, UINT8 data) { static UINT8 received_bytes = 0; // All range checking etc removed for brevity buffer[received_bytes] = data; received_bytes++; // Using the struc only way adds lots of logic that relates "byte 0" to size // "byte 1" to cmd, etc... } void packet_handler(PACKET_T* packet) { // Process the fields in a readable manner if(packet->fields.size > TOO_BIG) { // handle error... } if(packet->fields.cmd == CMD_X) { // do stuff.. } }

Editar El comentario sobre endianness y struct padding son válidos, y grandes preocupaciones. He usado este cuerpo de código casi por completo en software integrado, la mayoría de los cuales tenía el control de ambos extremos de la tubería.


Muchos usos. Simplemente haga grep union /usr/include/* o en directorios similares. La mayoría de los casos, la union está envuelta en una struct y un miembro de la estructura indica a qué elemento de la unión debe acceder. Por ejemplo, checkout man elf para implementaciones de la vida real.

Este es el principio básico:

struct _mydata { int which_one; union _data { int a; float b; char c; } foo; } bar; switch (bar.which_one) { case INTEGER : /* access bar.foo.a;*/ break; case FLOATING : /* access bar.foo.b;*/ break; case CHARACTER: /* access bar.foo.c;*/ break; }


Un ejemplo simple y muy útil, es ...

Imagina:

tiene una uint32_t array[2] y desea acceder al 3er y 4to byte de la cadena de bytes. podría hacer *((uint16_t*) &array[1]) . ¡Pero esto rompe tristemente las reglas estrictas de aliasing!

Pero los compiladores conocidos le permiten hacer lo siguiente:

union un { uint16_t array16[4]; uint32_t array32[2]; }

Técnicamente esto sigue siendo una violación de las reglas. Pero todos los estándares conocidos soportan este uso.


Utilicé union cuando estaba codificando para dispositivos embebidos. Tengo C int que es de 16 bits de largo. Y necesito recuperar los 8 bits más altos y los 8 bits más bajos cuando necesito leer desde / almacenar a EEPROM. Así que usé de esta manera:

union data { int data; struct { unsigned char higher; unsigned char lower; } parts; };

No requiere cambios, por lo que el código es más fácil de leer.

Por otro lado, vi un código antiguo de stl de C ++ que usaba union para stl allocator. Si está interesado, puede leer el código fuente de sgi stl . Aquí hay una parte de esto:

union _Obj { union _Obj* _M_free_list_link; char _M_client_data[1]; /* The client sees this. */ };


Yo diría que hace que sea más fácil reutilizar la memoria que se puede usar de diferentes maneras, es decir, guardar la memoria. Por ejemplo, te gustaría hacer alguna estructura "variante" que pueda guardar una cadena corta y un número:

struct variant { int type; double number; char *string; };

En un sistema de 32 bits, esto daría lugar a que se utilicen al menos 96 bits o 12 bytes para cada instancia de variant .

Usando una unión puede reducir el tamaño a 64 bits u 8 bytes:

struct variant { int type; union { double number; char *string; } value; };

Puede ahorrar aún más si desea agregar más tipos de variables diferentes, etc. Puede ser cierto, que puede hacer cosas similares lanzando un puntero al vacío, pero la unión lo hace mucho más accesible al igual que el tipo seguro. Estos ahorros no parecen ser enormes, pero está ahorrando un tercio de la memoria utilizada para todas las instancias de esta estructura.


  • Un archivo que contiene diferentes tipos de registro.
  • Una interfaz de red que contiene diferentes tipos de solicitud.

Echa un vistazo a esto: X.25 manejo de comandos de búfer

Uno de los muchos comandos X.25 posibles se recibe en un búfer y se maneja en su lugar utilizando una UNION de todas las estructuras posibles.