Uniones - Union

¿Qué es una union?

Una union es un tipo de dato compuesto, parecido a un struct, pero con una diferencia clave:

En un struct:

Cada campo tiene su propio espacio.

En una union:

Hay un solo bloque de memoria, reutilizado por todos los campos. Es decir:

Idea clave: Una union puede guardar una cosa u otra, pero nunca varias a la vez.

¿Para qué se usan?

Principalmente para:

Declaración

Sintaxis básica:


union Nombre {
    tipo campo_1;
    tipo campo_2;
    . . .
    };

Ejemplo:


union Dato{
    int i;
    float f;
    char c;
    };

Esto no significa que tenga espacio para los tres, sino solo para el más grande (sizeof(tipo))

Declarar variables de una union:


union Dato d;

O todo junto:


union Dato {
    int i;
    float f;
}d;

Acceso a los campos:

Se accede igual que en un struct con el operador "."


d.i = 10;
d.f = 3.14f;

ADVERTENCIA: Si escribimos un campo y luego leemos otro, el resultado es indefinido (UB).

Dimensión y ubicación en memoria:

sizeof de una union.

La dimensión de una union es la del miembro más grande.


union Ejemplo {
  char c;
  int i;
  double d;
};

int main(void)
{
  printf("sizeof(union Ejemplo) = %zu\n", sizeof(union Ejemplo));
  
  return 0;
}

Va a valer sizeof(double) (o más si hay alineación).

El tamaño de una union es el del miembro más grande, o mayor si el compilador agrega padding por alineación.

Dirección de los campos:

Todos los campos empiezan en la misma dirección:


union U {
  int i;
  float f;
};
union U u;
printf("&u\t = %p\n", (void *)&u); // Direccion de u
printf("&u.i\t = %p\n", (void *)&u.i); // Direccion del campo i
printf("&u.f\t = %p\n", (void *)&u.f); // Direccion del campo f

Las direcciones serán iguales!

Pasaje de miembros de una union a funciones

Podemos pasar un campo como cualquier variable:


void imprimir_int(int x)
{
  printf("x = %d\n", x);
}

Uso:


union Dato d;
d.i = 20;
imprimir_int(d.i);

Pasaje de una union completa a una función

Por valor (Copia):


void mostrar(union Dato d)
{
  printf("d.i = %d\n", d.i);
}

Uso:


union Dato d;
d.i = 10;
mostrar(d);

Por referencia (recomendado) - Modifica la original


void modificar(union Dato *d)
{
  d->i = 50;
}

Uso:


modificar(&d);

Retorno de una union desde una función


union Dato crear_dato(void)
{
  union Dato d;
  d.i = 100;
  return d;
}

Uso:


union Dato d = crear_dato();

Uniones dentro de estructuras (IMPORTANTE):

Este es el uso más común en la práctica.


struct Registro {
  int tipo;
  union {
      int i;
      float f;
      char c;
  } dato;
};

Uso:


struct Registro r;

r.tipo = 1; // Asignacion del campo tipo
r.dato.i = 42 // Asignacion del campo i de la union Dato

Acá el campo tipo indica como interpretar la union, patrón clásico: tagged union

Uniones de struct

Es una union cuyos miembros son struct. La variable puede contener una estructura u otra, pero nunca ambas al mismo tiempo, y todas comparten la misma memoria. Es el mismo concepto de union, pero el "bloque alternativo" ahora es una estructura completa.

¿Para qué se usa?

Declaración de una union de struct

Declarar las struct:


struct Alumno {
  int legajo;
  float promedio;
};

struct Docente {
  int id;
  int horas;
};

Declarar la union:


union Persona {
  struct Alumno alumno;
  struct Docente docente;
};

La memoria de union Persona será:

max(sizeof(struct Alumno), sizeof(struct Docente));

Uso:


struct Registro {
  int tipo;  // 0 = Alumno, 1 = Docente
  union Persona persona;
};

Diferencia clave entre struct vs union

Característica struct union
Memoria Cada campo tiene su propio espacio Todos los campos comparten el mismo espacio
Uso típico Datos simultáneos Datos alternativos
Dimensión Suma de los tamaños de los campos Tamaño del campo más grande

Errores comunes:

Teoría aplicada

Makefile:


CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -pedantic

PROG = union
OBJ = main.o

$(PROG): $(OBJ)
	$(CC) $(OBJ) -o $(PROG)

$(OBJ): main.c
	$(CC) $(CFLAGS) -c main.c

.PHONY: clean
clean:
	del $(OBJ) $(PROG).exe

Código:


// gcc -std=c11 -Wall -Wextra -pedantic main.c -o union

#include <stdio.h>

/* ---- UNION ----*/
union Dato {
    int i;
    float f;
    char c;
};

/* ---- FUNCIONES ---- */

/* Pasaje por valor */
void mostrar_union(union Dato d)
{
    printf("[mostrar_union] d.i = %d\n", d.i);
}

/* Pasaje por referencia */
void modificar_union(union Dato *d) // d es un puntero a union Dato
{
    d->i = 100;
}

/* Retorno de una union */
union Dato crear_union(void)
{
    union Dato d;
    d.i = 200;

    return d;
}

/* ---- UNION EN STRUCT ---- */
struct Registro {
    int tipo; // 0 = int, 1 = float, 2 = char
    union {
        int i;
        float f;
        char c;
    } dato;
};

/* ---- UNION DE STRUCT ---- */
struct Alumno {
    int legajo;
    float promedio;
};

struct Docente {
    int id;
    int horas;
};

union Persona {
    struct Alumno alumno;
    struct Docente docente;
};

struct  RegistroPersona {
    int tipo; // 0 = Alumno, 1 = Docente
    union Persona dato;
};

int main(void)
{
    union Dato d; // Declaracion de Variable d del tipo union Dato

    /* ---- ESCRITURA Y LECTURA DEL MISMO CAMPO ---- */
    d.i = 65; // Asignacion del campo i de la union Dato d

    printf("d.i = %d\n", d.i); // Mostrar el valor del campo i de la union Dato d
    printf("d.c = %c (misma memoria)\n", d.c); // Distinta interpretacion, para la misma memoria, aca es ASCII 65 = 'A' 

    /* ---- ESCRITURA DE OTRO CAMPO (CAMBIA LA INTERPRETACION) ---- */
    d.f = 3.14f;
    printf("d.f = %f\n", d.f);

    /* ---- DIMENSION ---- */
    printf("sizeof(union Dato) = %zu bytes\n", sizeof(union Dato)); // Va a valer 4 bytes ya que sizeof(int) y sizeof(float) miden lo mismo 

    /* ---- DIRECCIONES ---- */
    printf("&d\t = %p\n", (void *)&d);
    printf("&d.i\t = %p\n", (void *)&d.i);
    printf("&d.f\t = %p\n", (void *)&d.f);
    printf("&d.c\t = %p\n", (void *)&d.c);

    /* ---- PASAJE POR VALOR ---- */
    d.i = 10;
    mostrar_union(d);

    /* ---- PASAJE POR REFERENCIA ----*/
    modificar_union(&d);
    printf("Luego de modificar_union: d.i = %d\n", d.i);

    /* ---- RETORNO DE UNION ---- */
    d = crear_union();
    printf("Luego de crear_union: d.i = %d\n", d.i);

    /* ---- UNION EN STRUCT ---- */
    struct Registro r;
    
    r.tipo = 0; // int
    r.dato.i = 42;

    if (r.tipo == 0) {
        printf("Registro contiene int: %d\n", r.dato.i);
    }

    /* ---- UNION DE STRUCT ---- */
    struct RegistroPersona rp;

    /* CASO ALUMNO */
    rp.tipo = 0;
    rp.dato.alumno.legajo = 1234; // struct.union.struct.campo
    rp.dato.alumno.promedio = 8.5f;

    if (rp.tipo == 0)
    {
        printf("Alumno - legajo: %d, promedio: %.2f\n", rp.dato.alumno.legajo, rp.dato.alumno.promedio);
    }

    /* CASO DOCENTE (reutiliza la misma memoria) */
    rp.tipo = 1;
    rp.dato.docente.id = 77;
    rp.dato.docente.horas = 20;

    if (rp.tipo == 1)
    {
        printf("Docente - id: %d, horas: %d\n",rp.dato.docente.id, rp.dato.docente.horas);
    }

    return 0;
}  
Ejercicios de Union