Inicialización de una unión en C

6 minutos de lectura

avatar de usuario
Sumit Cornelio

Encontré esta pregunta objetiva sobre el lenguaje de programación C. Se supone que la salida para el siguiente código es 0 2pero no entiendo por qué.

Explique el proceso de inicialización. Aquí está el código:

#include <stdio.h>

int main()
{
  union a
  {
    int x;
    char y[2];
  };
  union a z = {512};
  printf("\n%d %d", z.y[0], z.y[1]);
  return 0;
}

  • ¿Cuál es el resultado inesperado que obtienes?

    – Bergi

    5 de junio de 2015 a las 14:41

  • @Peter Mortensen, ¡tu edición invirtió el significado de la pregunta! Debería ser The output is supposed to be 0 2. I dont' get why!

    – edc65

    5 de junio de 2015 a las 15:17


  • @ edc65 Exactamente. Traté de arreglar eso, pero mi edición sugerida stackoverflow.com/review/suggested-edits/8336900 fue rechazada. Si tú también quieres probarlo, quizás tengas mejor suerte que yo…

    – Fabio dice Reincorporar a Mónica

    5 de junio de 2015 a las 15:20

  • Tenga en cuenta que la salida de la instrucción printf() será diferente dependiendo de si la arquitectura de hardware subyacente es Big Endian o Little Endian. La salida podría ser (suponiendo una arquitectura de 32 o 64 bits) (Little Endian) ‘0 2’ o (Big Endian) ‘0 0’.

    – usuario3629249

    5 de junio de 2015 a las 20:47

Voy a suponer que usa un sistema little endian donde sizeof int es 4 bytes (32 bits) y tamaño de un char es 1 byte (8 bits), y uno en el que los números enteros se representan en forma de complemento a dos. A union solo tiene el tamaño de su miembro más grande, y todos los miembros apuntan a esta pieza exacta de memoria.

Ahora, estás escribiendo en esta memoria un valor entero de 512.

512 en binario es 1000000000.

o en forma de complemento a dos de 32 bits:

00000000 00000000 00000010 00000000.

Ahora convierta esto a su representación little endian y obtendrá:

00000000 00000010 00000000 00000000
|______| |______|
   |         |
  y[0]      y[1]

Ahora vea lo anterior, lo que sucede cuando accede a él usando índices de un char formación.

De este modo, y[0] es 00000000 cual es 0,

y y[1] es 00000010 cual es 2.

  • Gracias Arjun. Simplemente no pensé en la representación binaria. Ahora lo entiendo. 🙂

    – Sumit Cornelio

    5 de junio de 2015 a las 9:31

  • accederá y más allá de sus límites, pero aún dentro de la unión (p. ej. y[3]) ser un comportamiento indefinido, de manera similar a los accesos a matrices normales?

    – nanofaradio

    5 de junio de 2015 a las 10:35

  • @hexafraction AFAIK a menos que sizeof(int) 3 * sizeof(char), y[3] es legal ya que es solo *(y + 3) y y + 3 es un domicilio legal ocupado por el sindicato.

    – Arjun Sreedharan

    5 de junio de 2015 a las 11:03

  • @haccks no, esa no es la pregunta. Probablemente miraste la pregunta editada. La pregunta no es muy clara. Pero asumí que es ¿Por qué se supone que la salida del siguiente código es 0 2 ? y creo que el comentario de OP aquí reivindica mi suposición.

    – Arjun Sreedharan

    5 de junio de 2015 a las 17:48


avatar de usuario
Gopi

La memoria asignada para la unión es del tamaño del tipo más grande de la unión, que es inten este caso. digamos el tamaño de int en su sistema es de 2 bytes entonces

512 estarán 0x200.

La representación se parece a:

0000 0010 0000 0000
|        |         |
------------------- 
Byte 1     Byte 0

Entonces el primer byte es 0 y el segundo es 2.(En sistemas Little Endian)

char es un byte en todos los sistemas.

Entonces el acceso z.y[0] y z.y[1] es acceso por byte.

z.y[0] = 0000 0000 = 0
z.y[1] = 0000 0010 = 2

Solo le estoy dando cómo se asigna la memoria y cómo se almacena el valor. Debe considerar los puntos a continuación, ya que la salida depende de ellos.

Puntos a tener en cuenta:

  1. La salida depende completamente del sistema.
  2. los Endianess y el sizeof(int) asuntos, que variarán a través de los sistemas.

PD: La memoria que ocupan ambos miembros es la misma en unión.

  • Depende del orden de los bytes.

    – Werner Henze

    5 de junio de 2015 a las 8:21

  • Prueba esto: union a z={0x12345678}; printf("\n%x %x",z.y[0],z.y[1]); el resultado será más demostrativo.

    – Jabberwocky

    5 de junio de 2015 a las 8:22


  • @WernerHenze: de hecho, solo imprime 0 2 en sistemas little endian.

    – dummydev

    5 de junio de 2015 a las 8:22

  • Vale la pena mencionar que esta asignación funciona porque ambos miembros del sindicato ocupan la misma ubicación en la memoria.

    – Punto de hundimiento

    5 de junio de 2015 a las 8:23

  • @Quirliom eso depende del tamaño de (int). Un int de 32 bits en el sistema big endian producirá 0 0 como salida.

    – usuario268396

    5 de junio de 2015 a las 8:24

avatar de usuario
trucos

La norma dice que

6.2.5 Tipos:

Un tipo de unión describe un conjunto superpuesto no vacío de objetos miembrocada uno de los cuales tiene un nombre especificado opcionalmente y posiblemente un tipo distinto.

El compilador asigna solo el espacio suficiente para el miembro más grande, que se superponen entre sí dentro de este espacio.. En su caso, la memoria se asigna para int tipo de datos (suponiendo 4 bytes). La línea

union a z = {512};

inicializará el primer miembro de la unión zes decir x se convierte 512. En binario se representa como 0000 0000 0000 0000 0000 0010 0000 0000 en una máquina 32.

La representación de memoria para esto dependería de la arquitectura de la máquina. En una máquina de 32 bits será como (almacenar el byte menos significativo en la dirección más pequeña– Little Endian)

Address     Value
0x1000      0000 0000
0x1001      0000 0010
0x1002      0000 0000 
0x1003      0000 0000

o similar (almacene el byte más significativo en la dirección más pequeña — gran endian)

Address     Value
0x1000      0000 0000
0x1001      0000 0000
0x1002      0000 0010 
0x1003      0000 0000

z.y[0] accederá al contenido en direcciones 0x1000 y z.y[1] accederá al contenido en la dirección 0x1001 y esos contenidos dependerán de la representación anterior.
Parece que su máquina es compatible con la representación de Little Endian y, por lo tanto, z.y[0] = 0 y z.y[1] = 2 y la salida seria 0 2.

Pero, debes tener en cuenta que nota al pie 95 de la sección 6.5.2.3 Establece que

Si el miembro utilizado para leer el contenido de un objeto de unión no es el mismo que el último miembro utilizado para almacenar un valor en el objetola parte adecuada de la representación de objeto del valor se reinterpreta como una representación de objeto en el nuevo tipo, como se describe en 6.2.6 (un proceso que a veces se denomina ”tipo de juego de palabras”). Esta podría ser una representación trampa..

avatar de usuario
shreyans800755

El tamaño de la unión se deriva del tamaño máximo para contener un solo elemento de la misma. Entonces, aquí está el tamaño de int.

Suponiendo que sean 4 bytes/int y 1 bytes/char, podemos decir: sizeof union a = 4 bytes.

Ahora, veamos cómo se almacena realmente en la memoria:

Por ejemplo, una instancia de la unión, ase almacena en 2000-2003:

  • 2000 -> último byte (4º/menos significativo/más a la derecha) de int x, y[0]

  • 2001 -> 3er byte de int x, y[1]

  • 2002 -> 2do byte de int x

  • 2003 -> 1er byte de int x (más significativo)

Ahora, cuando dices z=512:

ya que z = 0x00000200,

  • METRO[2000] = 0x00

  • METRO[2001] = 0x02

  • METRO[2002] = 0x00

  • METRO[2003] = 0x00

Entonces, ¿por qué imprimes?[0] y yo[1]imprimirá datos M[2000] y M[2001] que es 0 y 2 en decimal respectivamente.

Para miembros automáticos (no estáticos), la inicialización es idéntica a la asignación:

union a z;
z.x = 512;

¿Ha sido útil esta solución?