Unions e Enums

Uma union é como um cabo de carregamento multiuso — você só pode usar uma interface por vez, mas economiza espaço. Um enum é como um número de cardápio — usar nomes em vez de números puros torna o código mais legível.

Definição de union e Memória Compartilhada

Todos os membros de uma union compartilham a mesma memória. Seu tamanho é igual ao tamanho do maior membro. Apenas um membro pode ser usado por vez.

C
union Data {
    int i;
    float f;
    char str[4];
};

printf("%zu\n", sizeof(union Data));
TEXT
4

Após atribuir ao membro int, ler o membro float dá uma interpretação diferente da mesma memória:

C
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
TEXT
i = 42
f = 0.000000
⚠️ Atenção: Ler um membro diferente daquele que foi escrito por último é comportamento definido pela implementação (depende da plataforma e dos tipos). Na prática, use um campo de tag para distinguir qual membro está atualmente válido.

union vs. struct

Característica struct union
Memória Cada membro tem seu próprio espaço Todos os membros compartilham o mesmo espaço
Tamanho Soma de todos os tamanhos dos membros (com padding de alinhamento) Tamanho do maior membro
Acesso simultâneo Todos os membros podem ser acessados ao mesmo tempo Apenas um membro por vez
Modificação Alterar um não afeta os outros Alterar um afeta todos
C
struct SData {
    int i;
    float f;
    char str[4];
};

union UData {
    int i;
    float f;
    char str[4];
};

printf("struct: %zu\n", sizeof(struct SData));
printf("union:  %zu\n", sizeof(union UData));
TEXT
struct: 12
union:  4

unions com Tag

Na prática, unions são tipicamente pareadas com um campo de tag que indica qual tipo está armazenado no momento:

C
typedef struct {
    int type;
    union {
        int int_val;
        float float_val;
        char str_val[32];
    } value;
} Variant;

void print_variant(const Variant *v) {
    switch (v->type) {
        case 0: printf("int: %d\n", v->value.int_val); break;
        case 1: printf("float: %f\n", v->value.float_val); break;
        case 2: printf("str: %s\n", v->value.str_val); break;
    }
}

Exemplo

C
#include <stdio.h>

typedef struct {
    int kind;
    union {
        int ival;
        double dval;
    } u;
} Number;

void print_number(const Number *n) {
    if (n->kind == 0) {
        printf("int: %d\n", n->u.ival);
    } else {
        printf("double: %.2f\n", n->u.dval);
    }
}

int main(void) {
    Number a = {0, .u.ival = 42};
    Number b = {1, .u.dval = 3.14};
    print_number(&a);
    print_number(&b);
    return 0;
}
▶ Experimente
TEXT
int: 42
double: 3.14

Definição e Uso de enum

Enumerações usam nomes em vez de constantes inteiras, melhorando a legibilidade do código.

C
enum Color {
    RED,
    GREEN,
    BLUE
};

enum Color favorite = GREEN;
if (favorite == GREEN) {
    printf("Verde selecionado\n");
}
TEXT
Verde selecionado

Especificando Valores de enum

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
TEXT
MON=1 SAT=100 SUN=101
💡 Dica: Constantes enum sem valor explícito assumem automaticamente o valor anterior + 1. A primeira assume 0 por padrão.

enum com switch

C
enum Direction { UP, DOWN, LEFT, RIGHT };

void move(enum Direction d) {
    switch (d) {
        case UP:    printf("Mover para cima\n"); break;
        case DOWN:  printf("Mover para baixo\n"); break;
        case LEFT:  printf("Mover para esquerda\n"); break;
        case RIGHT: printf("Mover para direita\n"); break;
    }
}
⚠️ Atenção: Enums são baseados em int — você pode atribuir qualquer valor inteiro, e o compilador pode não alertar. Não abuse desse recurso.

Valores int Subjacentes dos Enums

Constantes enum são essencialmente constantes nomeadas do tipo int.

C
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
TEXT
200 404 500

Variáveis enum podem ser atribuídas a partir de inteiros e para inteiros (em C), mas é melhor usar valores enum para consistência de tipo:

C
enum Status s = OK;
int code = s;
💡 Dica: Prefira enums em vez de #define para constantes: enums têm informação de tipo, exibem nomes simbólicos em depuradores e têm escopo mais controlável.

Usando union para Detectar Endianess

Endianess refere-se à ordem dos bytes de dados multibyte na memória. Uma union facilita a detecção:

C
int is_little_endian(void) {
    union {
        int i;
        char c;
    } u;
    u.i = 1;
    return u.c == 1;
}

Se o byte mais baixo é armazenado no endereço mais baixo (u.c == 1), é little-endian; caso contrário, é big-endian.

Exemplo

C
#include <stdio.h>

typedef union {
    unsigned int i;
    unsigned char bytes[4];
} EndianTest;

void print_byte_order(void) {
    EndianTest t;
    t.i = 0x12345678;
    printf("Ordem dos bytes na memoria: ");
    for (int i = 0; i < 4; i++) {
        printf("%02X ", t.bytes[i]);
    }
    printf("\n");
    if (t.bytes[0] == 0x78) {
        printf("Little Endian\n");
    } else {
        printf("Big Endian\n");
    }
}

int main(void) {
    print_byte_order();
    return 0;
}
▶ Experimente
TEXT
Ordem dos bytes na memoria: 78 56 34 12
Little Endian
🔥 Erro Comum: x86/ARM são tipicamente little-endian, enquanto a transmissão em rede usa big-endian (ordem de bytes de rede). Entender endianess é essencial para análise de protocolos e manipulação de formatos de arquivo.

union para Economia de Memória na Prática

Quando uma struct tem múltiplos campos mutuamente exclusivos, use uma union para economizar espaço:

C
typedef struct {
    int type;
    union {
        struct { int width, height; } rect;
        struct { int radius; } circle;
        struct { int side; } square;
    } shape;
} Figure;

Figure f;
f.type = 1;
f.shape.circle.radius = 10;

Apenas uma das três formas é usada por vez. A union compartilha a memória, e o tamanho da struct é determinado pela maior forma.

❓ Perguntas Frequentes

P: Uma union pode usar múltiplos membros ao mesmo tempo? R: Não. Membros de union compartilham memória — escrever um sobrescreve os outros. Você só deve ler o membro que foi escrito por último.

P: Posso imprimir um nome de enum com %s? R: Não. Valores de enum são inteiros. Para imprimir nomes, você precisa escrever uma função ou array de mapeamento, ex: const char *names[] = {"RED","GREEN","BLUE"}.

P: Qual a diferença entre enum e #define para constantes? R: enum tem um tipo (int), depuradores podem exibir nomes simbólicos e o escopo é mais controlável. #define é uma substituição do pré-processador sem informação de tipo. Prefira enum.

P: Por que usar union para detectar endianess é melhor que cast de ponteiro? R: São essencialmente a mesma coisa, mas a abordagem com union está mais alinhada com o espírito de segurança de tipos do C, evita problemas com a regra de strict aliasing e é mais legível.

📖 Resumo

📝 Exercícios

  1. Defina union IPAddress contendo unsigned int addr e unsigned char bytes[4]. Digite um valor inteiro de IP e exiba-o tanto como inteiro quanto como quatro bytes separados
  2. Defina um enum enum Season { SPRING, SUMMER, AUTUMN, WINTER } e escreva uma função que retorne a string do nome da estação correspondente ao valor do enum
  3. Use uma tagged union para implementar uma struct simples de forma geométrica com uma tag de tipo e union (retângulo/círculo), depois escreva uma função para calcular a área
100%